A simplified Trading API platform built with Node.js and Express for the Bajaj Broking Campus Hiring Assignment.
- Overview
- Features
- Architecture
- Setup Instructions
- API Endpoints
- Order Execution Flow
- Assumptions
- Sample Usage
- Running Tests
- Project Structure
This project implements a Wrapper SDK around a simplified Trading API platform. It simulates core trading workflows without real market connectivity.
- View available financial instruments (stocks)
- Place buy and sell orders (MARKET/LIMIT)
- Check order status
- View executed trades
- Fetch portfolio holdings and P&L
- ✅ Instrument APIs - Fetch tradable instruments with filtering
- ✅ Order Management - Place, view, and cancel orders
- ✅ Trade History - View all executed trades
- ✅ Portfolio Management - Holdings, balance, and P&L tracking
- ✅ Swagger Documentation - Interactive API docs at
/api-docs - ✅ Winston Logging - Application and HTTP request logging
- ✅ Centralized Error Handling - Consistent error responses
- ✅ Request Validation - Using express-validator
- ✅ Unit Tests - Jest + Supertest for API testing
- ✅ Order Execution Simulation - Immediate execution for MARKET orders
| Component | Technology |
|---|---|
| Runtime | Node.js |
| Framework | Express.js |
| Validation | express-validator |
| Documentation | Swagger (swagger-jsdoc + swagger-ui-express) |
| Logging | Winston + Morgan |
| Testing | Jest + Supertest |
| Storage | In-memory (JavaScript Maps) |
-
In-Memory Storage: Used JavaScript Maps for simplicity and fast access. In production, this would be replaced with a database (PostgreSQL, MongoDB).
-
Service Layer Pattern: Business logic is separated into services (instrumentService, orderService, portfolioService, tradeService) for better maintainability.
-
Centralized Error Handling: All errors flow through a single error handler middleware for consistent API responses.
-
Mock Authentication: A middleware injects
userId: "user123"into all requests, simulating authenticated sessions. -
Immediate Order Execution: MARKET orders execute instantly. LIMIT orders execute if price conditions are met at order placement time.
- Node.js (v16 or higher)
- npm (v8 or higher)
-
Clone/Extract the project
cd trading-sdk -
Install dependencies
npm install
-
Create environment file
cp .env.example .env
-
Start the server
Development mode (with auto-reload):
npm run dev
Production mode:
npm start
-
Access the application
- API Base URL:
http://localhost:3000/api/v1 - Swagger Docs:
http://localhost:3000/api-docs - Health Check:
http://localhost:3000/health
- API Base URL:
| Variable | Default | Description |
|---|---|---|
| PORT | 3000 | Server port |
| NODE_ENV | development | Environment (development/production/test) |
| LOG_LEVEL | info | Logging level |
| MOCK_USER_ID | user123 | Mock authenticated user ID |
| INITIAL_CASH_BALANCE | 500000 | Initial cash balance (₹) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/instruments |
Fetch all tradable instruments |
| GET | /api/v1/instruments/:symbol |
Get instrument by symbol |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/orders |
Place a new order |
| GET | /api/v1/orders |
Get all orders (with optional status filter) |
| GET | /api/v1/orders/:orderId |
Get order by ID |
| PUT | /api/v1/orders/:orderId/cancel |
Cancel an order |
| GET | /api/v1/orders/stats |
Get order statistics |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/trades |
Fetch all executed trades |
| GET | /api/v1/trades/:tradeId |
Get trade by ID |
| GET | /api/v1/trades/stats |
Get trade statistics |
| GET | /api/v1/trades/order/:orderId |
Get trades for an order |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/portfolio |
Fetch portfolio holdings |
| GET | /api/v1/portfolio/summary |
Get portfolio summary with P&L |
| GET | /api/v1/portfolio/balance |
Get cash balance |
| GET | /api/v1/portfolio/holdings/:symbol |
Get holding for specific symbol |
NEW → PLACED → EXECUTED
↓
CANCELLED
- Execute immediately at
lastTradedPrice - No price validation required
- State transition:
NEW → PLACED → EXECUTED
- BUY LIMIT: Execute if
lastTradedPrice <= limitPrice - SELL LIMIT: Execute if
lastTradedPrice >= limitPrice - If conditions not met, order stays in
PLACEDstatus
1. Validate request (symbol, quantity, price, orderType, orderStyle)
2. Check instrument exists
3. For BUY: Validate sufficient cash balance
For SELL: Validate sufficient holdings
4. Create order with status NEW
5. Transition to PLACED
6. Attempt execution based on order style
7. If executed:
- Create trade record
- Update portfolio (holdings + cash balance)
- Update order status to EXECUTED
- Deduct cash:
availableCash -= quantity × price - Add/update holding with new average price:
newAvgPrice = (oldQty × oldAvgPrice + newQty × newPrice) / (oldQty + newQty)
- Add cash:
availableCash += quantity × price - Reduce holding quantity (remove if zero)
-
Single User System: Mock authentication uses a single hardcoded user (
user123). -
In-Memory Storage: All data is stored in memory and lost on server restart.
-
Immediate Execution: Orders are evaluated for execution only at placement time. No background price monitoring.
-
No Partial Fills: Orders are either fully executed or not executed at all.
-
Static Prices: Instrument prices don't change during runtime (mock data).
-
NSE Default: If exchange not specified, defaults to NSE.
-
Initial Balance: User starts with ₹500,000 cash.
-
Order Cancellation: Only
NEWandPLACEDorders can be cancelled. -
No Margin Trading: User can only buy with available cash.
-
No Short Selling: User can only sell holdings they own.
curl -X POST http://localhost:3000/api/v1/orders \
-H "Content-Type: application/json" \
-d '{
"symbol": "RELIANCE",
"exchange": "NSE",
"orderType": "BUY",
"orderStyle": "MARKET",
"quantity": 10
}'curl -X POST http://localhost:3000/api/v1/orders \
-H "Content-Type: application/json" \
-d '{
"symbol": "RELIANCE",
"exchange": "NSE",
"orderType": "SELL",
"orderStyle": "LIMIT",
"quantity": 5,
"price": 2500
}'curl http://localhost:3000/api/v1/portfolio/summarycurl http://localhost:3000/api/v1/instrumentsSee API_EXAMPLES.md for complete request/response examples.
# Run all tests
npm test
# Run tests with coverage
npm test -- --coverage
# Run tests in watch mode
npm run test:watch- Order placement (MARKET/LIMIT)
- Order execution logic
- Balance and holdings validation
- Order cancellation
- Portfolio calculations
- Error scenarios
trading-sdk/
├── server.js # Entry point
├── package.json
├── .env.example
├── README.md
├── API_EXAMPLES.md
│
├── src/
│ ├── config/
│ │ ├── constants.js # Order states, types, HTTP codes
│ │ └── swagger.js # Swagger configuration
│ │
│ ├── models/
│ │ ├── storage.js # In-memory Maps
│ │ └── mockData.js # Mock instruments initialization
│ │
│ ├── services/
│ │ ├── instrumentService.js # Instrument business logic
│ │ ├── orderService.js # Order business logic
│ │ ├── portfolioService.js # Portfolio business logic
│ │ └── tradeService.js # Trade business logic
│ │
│ ├── controllers/
│ │ ├── instrumentController.js
│ │ ├── orderController.js
│ │ ├── portfolioController.js
│ │ └── tradeController.js
│ │
│ ├── routes/
│ │ └── index.js # All API routes
│ │
│ ├── middleware/
│ │ ├── errorHandler.js # Centralized error handling
│ │ ├── validator.js # Request validation
│ │ ├── authMock.js # Mock authentication
│ │ └── logger.js # Winston logger
│ │
│ └── utils/
│ └── errorClasses.js # Custom error classes
│
├── tests/
│ ├── order.test.js # Order API tests
│ └── portfolio.test.js # Portfolio API tests
│
└── logs/
├── error.log # Error logs
└── combined.log # All logs
All errors follow a consistent format:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human readable error message"
}
}| Code | HTTP Status | Description |
|---|---|---|
| VALIDATION_ERROR | 400 | Request validation failed |
| INSUFFICIENT_BALANCE | 400 | Not enough cash for BUY |
| INSUFFICIENT_HOLDINGS | 400 | Not enough shares for SELL |
| INSTRUMENT_NOT_FOUND | 404 | Symbol not found |
| ORDER_NOT_FOUND | 404 | Order ID not found |
| ORDER_CANNOT_BE_CANCELLED | 400 | Order already executed/cancelled |
| PRICE_REQUIRED_FOR_LIMIT | 400 | Price missing for LIMIT order |
| Symbol | Exchange | Last Traded Price (₹) |
|---|---|---|
| RELIANCE | NSE | 2,450.75 |
| TCS | NSE | 3,425.50 |
| INFY | NSE | 1,456.25 |
| HDFCBANK | NSE | 1,678.90 |
| ICICIBANK | NSE | 1,045.60 |
| SBIN | NSE | 625.30 |
| WIPRO | NSE | 512.45 |
| LT | NSE | 2,890.15 |
| BAJAJFINSV | NSE | 1,567.80 |
| MARUTI | NSE | 3,150.25 |
| ASIANPAINT | NSE | 2,845.60 |
| AXISBANK | NSE | 1,125.75 |
| BHARTIARTL | NSE | 945.20 |
| BRITANNIA | NSE | 4,850.90 |
| COALINDIA | NSE | 385.45 |
| RELIANCE | BSE | 2,448.50 |
| TCS | BSE | 3,422.75 |
| HDFCBANK | BSE | 1,676.40 |