Building A Centralized Exchange (Zerodha)
In this YouTube lecture, Harkirat explores building a centralized exchange like Zerodha. He covers the functional requirements, order book management, order matching, and the importance of low latency
High-Level Requirements of an Exchange
1] User Registration and KYC
Users need to sign up and complete the Know Your Customer (KYC) process
KYC involves providing identification documents like Aadhaar or PAN card
This ensures users have a Demat account and are Indian citizens
2] Balances and Funds Management
Users can view their current balances, including cash and stock holdings
Initially, users have zero cash balance and no stock holdings
Users can transfer funds from their bank account to the exchange
3] Trading Functionality
Users can buy and sell listed stocks on the exchange
Buying stocks deducts the cost from the user's cash balance and credits the stocks
Selling stocks removes the stocks from the user's holdings and adds the sale proceeds to the cash balance
4] Order Types
Users can place different types of orders, such as limit orders and market orders (explained later)
5] Open Orders and Trade History
Users can view their open (pending) orders
Users can access their trade history, including executed orders
How Does an Exchange Work?
1] User Registration and Authentication
Users sign up and sign in to the exchange platform
2] KYC and Demat Account Linking
Users complete the KYC process by providing required documents
Users link their Demat account to the exchange
3] Order Placement
Users can place orders to buy or sell stocks listed on the exchange
Two main order types: limit orders and market orders (explained later)
4] Balance and Holdings Management
Users can view their cash balance and stock holdings
Cash balance is updated based on buy and sell transactions
Stock holdings are updated based on executed buy and sell orders
5] Order Execution and Settlement
Orders are matched and executed based on the exchange's order matching engine
Executed trades are settled, and balances are updated accordingly
Price Determination Through Supply and Demand
The price of a stock or any asset is determined by the principles of supply and demand. Here's how it works in our day to day lives:
1] Supply and Demand Imbalance
If there are more buyers (demand) than sellers (supply) for a particular stock, the price tends to rise
If there are more sellers than buyers, the price tends to fall
2] Buyer and Seller Competition
When there is high demand, buyers compete by offering higher prices to acquire the stock
When there is high supply, sellers compete by lowering prices to attract buyers
Land Buying Example
To illustrate the concept of supply and demand, consider the example of buying a plot of land:
Buyer's Offer: A buyer approaches a broker and expresses willingness to pay, say, ₹40 lakhs for a plot of land
Seller's Counteroffer: The broker contacts the seller (owner of the land), who counters with an offer to sell at ₹42 lakhs
Negotiation and Convergence: The buyer and seller negotiate until they reach an agreed-upon price, or the deal falls through
Multiple Buyers and Limited Supply: If there are multiple buyers interested in the same property (high demand) and limited supply, buyers may outbid each other. This drives up the price as buyers compete to acquire the property
Multiple Sellers and Limited Demand: Conversely, if there are multiple sellers and limited demand, sellers may lower their prices to attract buyers. This drives down the price as sellers compete to sell their properties
The principles of supply and demand apply to various assets, including stocks, real estate, and cryptocurrencies, determining their prices in the market.
Financial Jargons to Understand Exchanges
1] Order Books
An order book is a central record that displays the outstanding buy and sell orders for a particular asset, such as a stock or cryptocurrency. It provides a transparent view of the supply and demand for that asset at various price levels.
To understand order books better, let's consider an example:
Imagine there are eight identical plots of land in Indiranagar, Bangalore. If you are a buyer interested in purchasing one of these plots, you would approach a broker and place your best offer, say ₹40 lakhs.
The broker maintains an order book, which is a record of all the buy and sell orders for these plots. Your offer of ₹40 lakhs would be recorded as a "bid" in the order book, represented by a green circle.
Now, if a seller comes along and is willing to sell one of the plots for ₹42 lakhs, the broker would record this as an "ask" in the order book, represented by a red circle.
The order book would then display:
Bids (green circles): The prices buyers are willing to pay, arranged from highest to lowest.
Asks (red circles): The prices sellers are willing to accept, arranged from lowest to highest.
As more buyers and sellers enter the market, their respective bids and asks are added to the order book. The goal is for the bids and asks to converge, where a buyer's bid matches a seller's ask. At this point, a transaction can occur, and the respective orders are removed from the order book.
For example, if a buyer increases their bid to ₹41 lakhs, and a seller lowers their ask to ₹41.5 lakhs, the spread between the bid and ask narrows. Eventually, when a bid and ask match, the transaction is executed, and the asset (in this case, the plot of land) is transferred from the seller to the buyer.
The order book provides transparency by displaying the current supply and demand for an asset at various price levels. It helps buyers and sellers make informed decisions based on the prevailing market conditions.
Understanding order books is crucial in comprehending the price discovery mechanism in financial markets, as the last executed transaction price becomes the current market price for that asset.
2] Price of a Stock
The price of a stock is determined by the last executed order in the exchange's order book. Here's how it works:
Each stock exchange (e.g., Zerodha, Groww) maintains its own order book for a particular stock.
The order book records all the outstanding buy (bid) and sell (ask) orders for that stock at various price levels.
When a buy order matches a sell order, a transaction is executed at an agreed-upon price.
The price at which this last transaction occurred becomes the current market price of the stock on that exchange.
For example, if the last executed order for Tata Steel on Zerodha was at ₹200.10, then the current market price of Tata Steel on Zerodha would be ₹200.10. However, on a different exchange like Groww, the last executed order for Tata Steel might have been at ₹200.05, making that the current market price on Groww.
While different exchanges may have slightly different prices for the same stock at any given time, the actual real-world price is typically calculated by taking inputs from all major exchanges and determining a consolidated price.
Thus, the price of a stock is not an average of past transactions or a predetermined value. It is simply the last price at which a buy order and a sell order were matched and executed on a particular exchange's order book.[1] This price represents the fair market value of the stock at that moment, as determined by the forces of supply and demand.
3] Limit Order
A limit order is a type of order where the trader specifies the maximum price they are willing to buy or the minimum price they are willing to sell a particular quantity of an asset.
When placing a limit order, the trader sets:
The quantity of the asset (e.g., 10 stocks of Apple)
The limit price at which they want to buy or sell
For example, a buy limit order could be "Buy 10 stocks of Apple at $150 or lower per share." This order will only execute if the market price is $150 or less. If the market price is higher than $150, the order will remain in the order book until the price drops to $150 or lower, or until the order is canceled.
Similarly, a sell limit order could be "Sell 10 stocks of Apple at $160 or higher per share." This order will only execute if the market price is $160 or higher. If the market price is lower than $160, the order will remain in the order book until the price rises to $160 or higher, or until the order is canceled.
Limit orders provide traders with control over the price at which they are willing to execute a trade. However, there is a risk that the order may not execute if the market price never reaches the specified limit price. Hence, they are typically used by traders who have a specific price target in mind and are willing to wait for the market to reach that price level before executing the trade.
4] Market Order
A market order is a type of order where the trader instructs the broker or exchange to execute the trade immediately at the best available market price.
When placing a market order, the trader specifies only the quantity of the asset they want to buy or sell. The price is determined by the current market conditions and the availability of orders in the order book.
For example, if a trader places a market order to buy 100 shares of a stock, the order will be filled by taking the best available ask prices (sell orders) from the order book until the entire quantity of 100 shares is filled.
Market orders are typically used by traders who prioritize immediate execution over a specific price. However, there is a risk that the market order may execute at an unfavorable price, especially in volatile or illiquid markets, as the order will be filled at the best available prices in the order book at the time of execution.
Market orders are often used by retail traders who want to buy or sell an asset quickly without having to specify a limit price. However, it's important to note that market orders can result in higher trading costs due to potential slippage (the difference between the expected price and the actual execution price).
Both limit orders and market orders are essential tools for traders, each with its own advantages and risks. Traders must carefully consider their trading strategies, risk tolerance, and market conditions when deciding which order type to use.
5] Liquidity and Market Depth
Liquidity and market depth refer to the ability of a market to handle large orders without significantly impacting the price of an asset. In the context of an exchange, these terms are closely related and describe the state of the order book.
Liquidity refers to the ease with which an asset can be bought or sold without causing a significant change in its price. A liquid market has a high volume of trading activity and a large number of buy and sell orders in the order book.
Market depth, on the other hand, refers to the quantity of orders available at different price levels in the order book. A market with high depth has a large number of orders concentrated around the current market price, allowing large orders to be executed without significantly moving the price.
When an order book has high liquidity and depth, it can absorb large buy or sell orders without causing significant price fluctuations. This is because there are enough orders on both sides of the book to match the incoming orders without depleting the available supply or demand.
For example, if a trader wants to buy 10,000 shares of a stock, a liquid market with high depth would have enough sell orders at or near the current market price to fill the order without significantly driving up the price. Conversely, if the market lacks depth, a large buy order could quickly consume the available sell orders, causing the price to rise sharply.
Exchanges strive to maintain high liquidity and market depth to ensure efficient price discovery and smooth trading operations. They often incentivize market makers, who are firms or individuals that provide liquidity by continuously placing buy and sell orders in the order book, to enhance market depth and liquidity.
Market makers are incentivized through various means, such as lower trading fees, rebates, or other incentives, to encourage them to provide liquidity and maintain tight bid-ask spreads (the difference between the highest buy order and the lowest sell order).
Maker and Taker
In the context of an exchange, the terms "maker" and "taker" refer to the roles played by traders in executing orders.
A maker is a trader who places an order in the order book, providing liquidity to the market. When a maker's order is matched and executed against an incoming order, they are considered the liquidity provider.
A taker, on the other hand, is a trader who submits an order that is immediately matched and executed against an existing order in the order book, removing liquidity from the market.
For example, if a trader places a buy order at the current market price, they are considered a taker because their order is immediately matched and executed against existing sell orders in the order book, consuming the available liquidity.
Exchanges often incentivize makers by charging them lower trading fees compared to takers. This is because makers are providing liquidity to the market, which is beneficial for the overall health and efficiency of the exchange. Takers, on the other hand, are charged higher fees because they are removing liquidity from the market.
The fee structure for makers and takers can vary across different exchanges and may also depend on factors such as trading volume, account type, or market conditions. Generally, the maker fee is lower than the taker fee, encouraging traders to provide liquidity to the order book.
By incentivizing makers and charging higher fees for takers, exchanges aim to maintain a healthy order book with sufficient liquidity, which ultimately benefits all market participants by facilitating efficient price discovery and trade execution.
It's important to note that the roles of maker and taker are not fixed for a particular trader. A trader can be a maker on one trade and a taker on another, depending on whether their order is adding or removing liquidity from the order book.
Why is Latency Important?
Latency is crucial in the context of exchanges and trading systems. Here's why it's important and how it's addressed for exchanges and traders:
For Exchanges:
Order Placement Time: Exchanges need to process incoming orders (limit or market) and add them to the order book as quickly as possible.
Order Book Updates: The order book is constantly changing, and exchanges must disseminate real-time updates to traders with minimal latency.
Order Acknowledgment Time: Traders need to know if their orders were accepted, filled, or partially filled in a timely manner.
For Traders:
Order Execution: Low latency is critical for traders, especially those engaged in high-frequency trading strategies, to execute orders at the desired price before the market moves.
Order Cancellation: Traders may need to cancel orders quickly in response to market changes, and high latency can lead to significant losses.
Order Priority: Some exchanges prioritize orders from traders who provide more liquidity, and low latency can give them an edge in order execution.
How is Latency Improved?
The ways to improve latency for exchanges as well as for traders are as follows:
For Exchanges:
Faster Programming Languages: Exchanges use low-level, high-performance languages like C, C++, and Rust for critical components to minimize latency.
Efficient Serialization/Deserialization: Optimizing data serialization and deserialization can significantly reduce latency in processing orders and updates.
In-Memory Order Books: Order books are often stored entirely in memory (RAM) instead of databases to minimize access latency, with mechanisms to recover from failures.
For Traders:
Proximity to Exchange Servers: Traders aim to be physically closer to exchange servers to reduce network latency.
Multiple Connections: Traders maintain multiple connections to exchanges and dynamically use the fastest one.
Fast Data Processing: Efficient serialization/deserialization and data processing libraries are used to handle real-time market data with minimal latency.
Low-Latency Prediction Models: Mathematical models are employed to predict current asset prices across exchanges, requiring low-latency data ingestion and processing.
Both exchanges and traders invest significant resources into minimizing latency, as even milliseconds can make a substantial difference in trading outcomes. Exchanges strive to provide a low-latency environment, while traders leverage various techniques and technologies to gain an edge in order execution and market data processing.
Building Exchange Orderbook
1] Requirements:
Support placing limit orders
Orders can be fully or partially executed/filled
Users can query the current order book depth (bids and asks)
Users can retrieve their account balance (cash and stock holdings)
Implement an assignment to handle market orders by first requesting a quote and then placing a limit order at the quoted price
2] Architecture:
HTTP Server
Use Node.js with Express.js framework
Written in TypeScript
Routes/Endpoints
/order
endpoint to place a limit orderInput: side (buy/sell), price, quantity, ticker/market, user ID
/balance
endpoint to retrieve user's balanceInput: user ID
Returns: user's cash balance and stock holdings
/depth
endpoint to retrieve the current order book depthReturns: bids (price and quantity) and asks (price and quantity)
Order Book
Core component to maintain the list of outstanding buy (bid) and sell (ask) orders
Orders matched based on price-time priority
Handle partial order fills
Order Matching Engine
Match buy and sell orders in the order book
Execute trades when a buy order matches a sell order (or vice versa)
User Accounts
Maintain user accounts to track cash balances and stock holdings
Update balances based on executed trades
Market Order Implementation (Assignment)
User first sends a request to
/quote
endpoint to get the current best priceUser then places a limit order at the quoted price via the
/order
endpoint, effectively executing a market order
The architecture involves setting up an HTTP server with specific routes/endpoints to handle order placement, balance retrieval, and order book depth querying. The core components include the order book, order matching engine, and user account management. The assignment focuses on implementing market order functionality by combining quote requests and limit order placement.
Technical Implementation
To implement the order book in a MERN stack (Node.js, Express, and TypeScript), we will follow a structured approach. First, we will set up a Node.js server using Express and TypeScript. Then, we will create the necessary data structures and functions to manage the order book, handle order placement, order matching, and user account management.
Let's initialize a TypeScript backend in Node.js and create an index.ts
file to start things off. In this file, we will import the required dependencies, set up the Express server, define routes for handling various requests (e.g., placing orders, retrieving balances, and fetching order book depth), and implement the core functionality of the order book system.
1] Imports and Setup
import express from "express";
import bodyParser from "body-parser";
export const app = express();
app.use(bodyParser({}));
The code imports the necessary modules:
express
for setting up the HTTP server andbody-parser
for parsing request bodies.An instance of the Express application is created and assigned to the
app
constant.body-parser
middleware is added to parse request bodies.
2] Type Definitions
interface Balances {
[key: string]: number;
}
interface User {
id: string;
balances: Balances;
};
interface Order {
userId: string;
price: number;
quantity: number;
}
The code defines several TypeScript interfaces:
Balances
: An object that maps asset names (strings) to their respective quantities (numbers).User
: An object representing a user, containing anid
(string) andbalances
(an instance of theBalances
interface).Order
: An object representing an order, containinguserId
(string),price
(number), andquantity
(number).
3] Global Variables
export const TICKER = "GOOGLE";
const users: User[] = [{
id: "1",
balances: {
"GOOGLE": 10,
"USD": 50000
}
}, {
id: "2",
balances: {
"GOOGLE": 10,
"USD": 50000
}
}];
const bids: Order[] = [];
const asks: Order[] = [];
TICKER
: A constant string representing the ticker symbol of the asset being traded (in this case, "GOOGLE").users
: An array ofUser
objects, representing the initial user accounts and their balances.bids
: An array ofOrder
objects, representing the outstanding buy orders (bids) in the order book.asks
: An array ofOrder
objects, representing the outstanding sell orders (asks) in the order book.
4] HTTP Endpoints
/order
(POST)
app.post("/order", (req: any, res: any) => {
const side: string = req.body.side;
const price: number = req.body.price;
const quantity: number = req.body.quantity;
const userId: string = req.body.userId;
const remainingQty = fillOrders(side, price, quantity, userId);
if (remainingQty === 0) {
res.json({ filledQuantity: quantity });
return;
}
if (side === "bid") {
bids.push({
userId,
price,
quantity: remainingQty
});
bids.sort((a, b) => a.price < b.price ? -1 : 1);
} else {
asks.push({
userId,
price,
quantity: remainingQty
})
asks.sort((a, b) => a.price < b.price ? 1 : -1);
}
res.json({
filledQuantity: quantity - remainingQty,
})
})
This endpoint handles the placement of limit orders.
It expects the following request body parameters:
side
(string, either "bid" or "ask"),price
(number),quantity
(number), anduserId
(string).The
fillOrders
function is called to match the incoming order against the existing orders in the order book.If the order is fully filled, a JSON response is sent with the
filledQuantity
equal to the originalquantity
.If the order is partially filled, the remaining quantity is added to the
bids
orasks
array, sorted by price.A JSON response is sent with the
filledQuantity
equal to the filled portion of the order.
/depth
(GET)
app.get("/depth", (req: any, res: any) => {
const depth: {
[price: string]: {
type: "bid" | "ask",
quantity: number,
}
} = {};
for (let i = 0; i < bids.length; i++) {
if (!depth[bids[i].price]) {
depth[bids[i].price] = {
quantity: bids[i].quantity,
type: "bid"
};
} else {
depth[bids[i].price].quantity += bids[i].quantity;
}
}
for (let i = 0; i < asks.length; i++) {
if (!depth[asks[i].price]) {
depth[asks[i].price] = {
quantity: asks[i].quantity,
type: "ask"
}
} else {
depth[asks[i].price].quantity += asks[i].quantity;
}
}
res.json({
depth
})
})
This endpoint returns the current order book depth, which is a representation of the outstanding bids and asks at various price levels.
It iterates through the
bids
andasks
arrays, aggregating the quantities at each price level.The resulting depth is returned as a JSON response, where the keys are prices, and the values are objects containing the
type
("bid" or "ask") and the aggregatedquantity
.
/balance/:userId
(GET)
app.get("/balance/:userId", (req, res) => {
const userId = req.params.userId;
const user = users.find(x => x.id === userId);
if (!user) {
return res.json({
USD: 0,
[TICKER]: 0
})
}
res.json({ balances: user.balances });
})
This endpoint retrieves the balance of a specific user.
It expects the
userId
as a path parameter.It finds the user object in the
users
array based on the provideduserId
.If the user is found, their
balances
object is returned as a JSON response.If the user is not found, a JSON response with
USD
andTICKER
(GOOGLE) balances set to 0 is returned.
/quote
(GET)
app.get("/quote", (req, res) => {
// TODO: Assignment
});
This endpoint is currently empty, but it is intended to handle quote requests for market orders (as per the assignment mentioned in the transcript).
5] Helper Functions
flipBalance
function flipBalance(userId1: string, userId2: string, quantity: number, price: number) {
let user1 = users.find(x => x.id === userId1);
let user2 = users.find(x => x.id === userId2);
if (!user1 || !user2) {
return;
}
user1.balances[TICKER] -= quantity;
user2.balances[TICKER] += quantity;
user1.balances["USD"] += (quantity * price);
user2.balances["USD"] -= (quantity * price);
}
This function is responsible for updating the balances of two users involved in a trade.
It takes the
userId1
,userId2
,quantity
, andprice
as arguments.It finds the corresponding user objects in the
users
array.It updates the
TICKER
(GOOGLE) andUSD
balances of the users based on the trade details.
fillOrders
function fillOrders(side: string, price: number, quantity: number, userId: string): number {
let remainingQuantity = quantity;
if (side === "bid") {
for (let i = asks.length - 1; i >= 0; i--) {
if (asks[i].price > price) {
continue;
}
if (asks[i].quantity > remainingQuantity) {
asks[i].quantity -= remainingQuantity;
flipBalance(asks[i].userId, userId, remainingQuantity, asks[i].price);
return 0;
} else {
remainingQuantity -= asks[i].quantity;
flipBalance(asks[i].userId, userId, asks[i].quantity, asks[i].price);
asks.pop();
}
}
} else {
for (let i = bids.length - 1; i >= 0; i--) {
if (bids[i].price < price) {
continue;
}
if (bids[i].quantity > remainingQuantity) {
bids[i].quantity -= remainingQuantity;
flipBalance(userId, bids[i].userId, remainingQuantity, price);
return 0;
} else {
remainingQuantity -= bids[i].quantity;
flipBalance(userId, bids[i].userId, bids[i].quantity, price);
bids.pop();
}
}
}
return remainingQuantity;
}
This function is responsible for matching an incoming order against the existing orders in the order book.
It takes the
side
(bid or ask),price
,quantity
, anduserId
as arguments.If the incoming order is a bid, it iterates through the
asks
array in reverse order (from highest price to lowest).If the ask price is higher than the bid price, it skips that ask order.
If the ask quantity is greater than the remaining quantity of the bid, it partially fills the ask order, updates the balances using
flipBalance
, and returns 0 (indicating the bid order is fully filled).If the ask quantity is less than or equal to the remaining quantity of the bid, it fully fills the ask order, updates the balances using
flipBalance
, removes the ask order from theasks
array, and continues to the next ask order.
If the incoming order is an ask, the process is similar but with the
bids
array and the order of price comparison reversed.If there are no matching orders or the incoming order cannot be fully filled, the remaining quantity is returned.
6] Order Book Management
The order book is represented by two arrays:
bids
andasks
.When a new order is placed, the
fillOrders
function attempts to match it against the existing orders in the order book.If the order is partially filled, the remaining quantity is added to the appropriate
bids
orasks
array, sorted by price.The
bids
array is sorted in descending order (highest price first), while theasks
array is sorted in ascending order (lowest price first).
7] User Balances
User balances are stored in the
users
array, where each user object has abalances
property.The
balances
property is an object that maps asset names (e.g., "GOOGLE" and "USD") to their respective quantities.When a trade is executed, the
flipBalance
function updates the balances of the involved users based on the trade details.
Understanding Tests (index.spec.ts)
Importing Dependencies
import { app, TICKER } from "../";
import request from "supertest";
This section imports the app
and TICKER
constants from the main application file (../index.ts
). It also imports the request
module from the supertest
library, which is used for testing HTTP servers.
Defining the Test Suite
describe("Basic tests", () => {
// Test cases...
});
This block defines a test suite named "Basic tests" using the describe
function provided by Jest.
Test Case: "verify initial balances"
it("verify initial balances", async () => {
let res = await request(app).get("/balance/1").send();
expect(res.body.balances[TICKER]).toBe(10);
res = await request(app).get("/balance/2").send();
expect(res.body.balances[TICKER]).toBe(10);
});
This test case verifies the initial balances of two users (with IDs "1" and "2") by sending GET requests to the /balance/:userId
endpoint using the request
function from supertest
. It expects the initial balance of the TICKER
asset (e.g., "GOOGLE") to be 10 for both users using the expect
function provided by Jest.
Test Case: "Can create tests"
it("Can create tests", async () => {
await request(app).post("/order").send({
type: "limit",
side: "bid",
price: 1400.1,
quantity: 1,
userId: "1"
});
// Additional order placements...
let res = await request(app).get("/depth").send();
expect(res.status).toBe(200);
expect(res.body.depth["1501"].quantity).toBe(5);
});
This test case creates three limit orders by sending POST requests to the /order
endpoint using the request
function from supertest
. It then sends a GET request to the /depth
endpoint to retrieve the order book depth and expects the response status to be 200 (OK) and the quantity at the price level 1501 to be 5.
Test Case: "ensures balances are still the same"
it("ensures balances are still the same", async () => {
let res = await request(app).get("/balance/1").send();
expect(res.body.balances[TICKER]).toBe(10);
});
This test case verifies that the balance of user "1" remains unchanged after creating the orders in the previous test case.
Test Case: "Places an order that fills"
it("Places an order that fills", async () => {
let res = await request(app).post("/order").send({
type: "limit",
side: "bid",
price: 1502,
quantity: 2,
userId: "1"
});
expect(res.body.filledQuantity).toBe(2);
});
This test case places a buy order (bid) for 2 units of the TICKER
asset at a price of 1502 for user "1" by sending a POST request to the /order
endpoint using the request
function from supertest
. It expects the response to indicate that the entire quantity of 2 units was filled.
Test Case: "Ensures orderbook updates"
it("Ensures orderbook updates", async () => {
let res = await request(app).get("/depth").send();
expect(res.body.depth["1400.9"]?.quantity).toBe(8);
});
This test case retrieves the order book depth by sending a GET request to the /depth
endpoint using the request
function from supertest
. It expects the quantity at the price level 1400.9 to be 8 (assuming the previous order filled 2 units from the ask order at 1400.9).
Test Case: "Ensures balances update"
it("Ensures balances update", async () => {
let res = await request(app).get("/balance/1").send();
expect(res.body.balances[TICKER]).toBe(12);
expect(res.body.balances["USD"]).toBe(50000 - 2 * 1400.9);
res = await request(app).get("/balance/2").send();
expect(res.body.balances[TICKER]).toBe(8);
expect(res.body.balances["USD"]).toBe(50000 + 2 * 1400.9);
});
This test case verifies the updated balances of users "1" and "2" after the order execution by sending GET requests to the /balance/:userId
endpoint using the request
function from supertest
. For user "1", it expects the TICKER
balance to be 12 (initial 10 + 2 units bought) and the USD balance to be decreased by the cost of the 2 units (2 * 1400.9). For user "2", it expects the TICKER
balance to be 8 (initial 10 - 2 units sold) and the USD balance to be increased by the proceeds from selling 2 units (2 * 1400.9).
This test suite uses the
supertest
library to send HTTP requests to the order book system implemented in the Node.js application and verifies the expected behavior by making assertions using theexpect
function provided by Jest. It covers various aspects of the order book system, including order placement, order matching, order book depth updates, and balance updates after order execution.