A Go trading engine with deterministic limit-order matching, risk checks, event persistence, Kafka publishing, Redis rate limiting, Binance market-data sync, Prometheus metrics, and a React dashboard for order entry and book monitoring.
Portfolio / learning project: This project simulates an exchange-style matching engine. It is not a production trading venue, broker, investment system, or financial service. Binance data is used as external market reference data only; orders submitted through the dashboard are matched by the internal engine.
- Features
- Tech Stack
- Architecture
- Services
- Project Structure
- Matching Engine
- Market Data
- Risk & Rate Limiting
- Persistence & Events
- API Endpoints
- Data Model
- Dashboard
- Setup
- Observability
- Known Limitations
- Future Improvements
- License
- Deterministic price-time-priority matching engine
- Limit and market order support
- Order cancellation
- Internal order book and trade tape
- Separate Binance market-data book
- Internal/Binance book switching in the dashboard
- Startup seed orders so the internal dashboard is not empty
- Risk checks for balances, positions, order size, and price collars
- Redis-backed Gin rate limiting
- PostgreSQL event, order, trade, position, and snapshot storage
- Kafka publishing for matching events
- Startup restore from snapshots, replayed events, and persisted trades
- Execution/slippage simulator using market-data snapshots
- WebSocket updates for internal order-book events
- WebSocket updates for external market data
- Prometheus metrics endpoint
- Docker Compose stack for API, frontend, Postgres, Redis, Kafka, Prometheus, and Grafana
- Language: Go 1.26
- Web Framework: Gin
- Frontend: React + Vite
- Database: PostgreSQL via pgx/v5
- Cache / Rate Limiting: Redis via go-redis/v9
- Messaging: Kafka via segmentio/kafka-go
- WebSockets: gorilla/websocket
- Market Data: Binance Spot depth WebSocket adapter
- Observability: Prometheus metrics + Grafana provisioning
- Architecture: Modular monolith backend + separate React dashboard + event-driven integrations
flowchart LR
Browser[React Dashboard]
subgraph API["trading-engine API :8080"]
Gin[Gin REST API]
WS[WebSocket endpoints]
Match[Matching Engine]
Risk[Risk Engine]
MD[Market Data Book]
Metrics[Prometheus Metrics]
end
Binance[Binance WebSocket]
Redis[(Redis)]
Postgres[(PostgreSQL)]
Kafka[/Kafka/]
Prometheus[Prometheus]
Grafana[Grafana]
Browser --> Gin
Browser <--> WS
Gin --> Risk
Risk --> Match
Gin <--> Redis
Match --> Postgres
Match --> Kafka
Binance --> MD
MD --> WS
Prometheus --> Metrics
Grafana --> Prometheus
The internal matching book and the Binance market-data book are deliberately separate.
Internal book = orders submitted to this engine
Binance book = external market reference data
The dashboard can switch between them, but Binance data is not inserted into the internal matching engine.
| Service | Port | Responsibility |
|---|---|---|
api |
8080 | REST API, WebSockets, matching, risk, metrics |
web |
5173 | React dashboard served by Nginx in Docker Compose |
postgres |
5432 | Event, order, trade, snapshot, account persistence |
redis |
6379 | Request rate limiting |
kafka |
9092 | Matching-event stream |
prometheus |
9090 | Metrics scraping |
grafana |
3000 | Metrics dashboarding |
Trading-Engine/
|-- cmd/
| `-- trading-engine/
| `-- main.go
|
|-- internal/
| |-- api/ # Gin handlers, REST routes, WebSockets
| |-- app/ # Application wiring and startup
| |-- domain/ # Shared domain types
| |-- execution/ # Market-order execution/slippage simulator
| |-- kafka/ # Kafka event publisher
| |-- marketdata/ # External market-data book, syncer, Binance feed
| |-- matching/ # Deterministic matching engine, replay, snapshots
| |-- observability/ # Prometheus metrics
| |-- orderbook/ # Price-level order book
| |-- postgres/ # pgx store and schema
| |-- ratelimit/ # Redis rate limiter
| `-- risk/ # Risk checks
|
|-- web/
| |-- src/
| | |-- main.jsx
| | `-- styles.css
| |-- Dockerfile
| |-- nginx.conf
| |-- package.json
| `-- vite.config.js
|
|-- deploy/
| |-- prometheus/
| `-- grafana/
|
|-- docker-compose.yml
|-- .env.example
|-- Dockerfile
|-- go.mod
|-- LICENSE
`-- README.md
The matching engine owns the internal order book.
Supported order types:
limit
market
Supported sides:
buy
sell
Matching follows price-time priority:
- Buy orders match against the lowest sell price.
- Sell orders match against the highest buy price.
- Resting orders remain in the internal book until matched or canceled.
- Events are assigned deterministic engine sequence numbers.
Startup seed orders currently populate BTC-USD so the dashboard has initial internal-book data.
Binance market data is consumed separately from the internal matching book.
Default configured symbols:
BTC-USDT
ETH-USDT
The frontend maps internal symbols to Binance symbols:
BTC-USD -> BTC-USDT
ETH-USD -> ETH-USDT
Binance price and quantity values are stored internally as scaled integers:
price scale: 100
quantity scale: 100000000
The dashboard displays these as human-readable decimal values.
The risk engine validates:
- Maximum order quantity
- Maximum position
- Buy-side cash balance
- Sell-side available position
- Optional price collars
Redis-backed rate limiting is applied through Gin middleware-style integration when Redis is available. If Redis is unavailable, the app logs the failure and continues without rate limiting.
PostgreSQL persistence includes:
- Matching events
- Orders
- Trades
- Engine snapshots
- Accounts
- Positions
Kafka is currently publisher-only in this repository. The API publishes matching events to the trading-engine-events topic for downstream services such as audit, analytics, settlement, or notifications. No Kafka consumer is implemented in this repo; PostgreSQL remains the source for recovery.
PostgreSQL event and snapshot persistence completes before an order response is returned, so restart recovery can restore the order book, trade tape, and recent event history. Kafka publishing is asynchronous and is not used as the recovery source.
On startup, the API restores the configured internal symbol from:
- The latest engine snapshot
- Matching events after that snapshot
- Persisted trades for the trade tape
Base URL:
http://localhost:8080
| Method | Path | Description |
|---|---|---|
| POST | /orders |
Submit an order |
| GET | /orders/:id |
Get recorded order |
| DELETE | /orders/:id |
Cancel a resting order |
POST /orders
{
"account_id": "demo",
"id": "buy-1",
"symbol": "BTC-USD",
"side": "buy",
"type": "limit",
"price": 100,
"quantity": 1
}| Method | Path | Description |
|---|---|---|
| GET | /orderbook/:symbol |
Internal best bid / best ask |
| GET | /trades/:symbol |
Internal trade tape |
| GET | /events/:symbol |
Recent internal event log |
Example:
GET /events/BTC-USD?limit=120
| Method | Path | Description |
|---|---|---|
| GET | /marketdata/:symbol |
External market-data snapshot |
Example:
GET /marketdata/BTC-USDT
| Path | Description |
|---|---|
/ws/orderbook/:symbol |
Internal order-book events |
/ws/marketdata/:symbol |
External market-data snapshots |
| Method | Path | Description |
|---|---|---|
| GET | /metrics |
Prometheus metrics |
erDiagram
ENGINE_EVENTS {
bigint sequence
int event_index
text event_type
text symbol
text order_id
text side
text order_type
bigint price
bigint quantity
text maker_order_id
text taker_order_id
}
ORDERS {
text id PK
text symbol
text side
text order_type
bigint price
bigint original_quantity
bigint remaining_quantity
text status
bigint accepted_sequence
bigint updated_sequence
timestamptz created_at
timestamptz updated_at
}
TRADES {
bigint sequence
int trade_index
text symbol
text maker_order_id
text taker_order_id
bigint price
bigint quantity
}
ENGINE_SNAPSHOTS {
text symbol
bigint sequence
jsonb orders
timestamptz created_at
}
ACCOUNTS {
text id PK
timestamptz created_at
}
POSITIONS {
text account_id
text symbol
bigint quantity
}
ORDERS ||--o{ TRADES : maker_or_taker
ENGINE_EVENTS ||--o{ TRADES : records
ACCOUNTS ||--o{ POSITIONS : has
The React dashboard supports:
- Market selection
- Internal/Binance book switching
- Internal order entry through the Order Ticket
- Trade tape
- Event log rehydrated from REST on load
- Session stats
- Live WebSocket updates
In Internal mode, the dashboard shows the Order Ticket, internal trades, internal volume, and matching events. The Order Ticket account field is editable, but the seeded demo account is the only default account; any other account ID returns account_not_found.
On page load, the dashboard fetches the latest order book, trade tape, and recent events, then appends live WebSocket events.
In Binance mode, the dashboard is read-only and shows external market depth. Orders are not sent to Binance.
- Go 1.26+
- Node.js
- Docker
- Docker Compose
git clone https://github.com/ErenKarakus1/Trading-Engine.git
cd Trading-Enginedocker compose up --buildExpected local addresses:
API: http://localhost:8080
Dashboard: http://localhost:5173
Prometheus: http://localhost:9090
Grafana: http://localhost:3000
Grafana credentials:
username: admin
password: admin
The API starts with a seeded demo account for local testing. The dashboard Order Ticket account field is editable, but demo is the only default account; any other account ID returns account_not_found unless additional accounts are added in code.
go mod download
go run ./cmd/trading-engineThe API runs on:
http://localhost:8080
Optional environment variables:
TRADING_ENGINE_ADDR=:8080
POSTGRES_DSN=postgres://trading_engine:trading_engine@localhost:5432/trading_engine?sslmode=disable
REDIS_ADDR=localhost:6379
KAFKA_BROKERS=localhost:9092
KAFKA_TOPIC=trading-engine-events
BINANCE_DOMAIN_SYMBOLS=BTC-USDT,ETH-USDT
A sample environment file is provided:
cp .env.example .envDocker Compose already includes working service defaults in docker-compose.yml, so copying .env.example is mainly useful for local non-Docker runs.
cd web
npm install
npm run devThe Vite dev server runs on:
http://127.0.0.1:5173
go test ./...Frontend build:
cd web
npm run buildThe API exposes Prometheus metrics at:
GET /metrics
Metrics include:
- HTTP request count and duration
- Accepted and rejected orders
- Executed trades
- Matching latency
- WebSocket connections and messages
- Market-data messages and reconnects
- Kafka consumer lag gauge placeholder for future consumers
Prometheus and Grafana are included in Docker Compose.
- This is a simulation and not a production exchange.
- There is no real authentication or user management.
- Demo account balances and positions are seeded in code and used for risk checks only; trades do not settle balances or positions.
- Kafka publishing is not implemented as a transactional outbox.
- Binance market data uses WebSocket depth snapshots and does not place orders on Binance.
- Binance connectivity depends on local network access.
- Market-data symbol support is configured at startup.
- PostgreSQL, Redis, and Kafka failures are logged and may disable related features.
- Startup restore is currently centered on the configured snapshot symbol.
- The dashboard is an operator UI, not a broker-grade trading frontend.
- Authentication and user management
- Persistent account management, balance updates, and trade settlement
- Admin tools for accounts, balances, and positions
- Transactional outbox for reliable Kafka publishing
- Historical replay UI
- Better multi-symbol market-data management
- More complete Binance REST snapshot bootstrapping
- Strategy simulation against external market data
- Order history and open-order views
- More Grafana dashboards
- Structured logging
- Integration tests with Docker containers
This project is licensed under the MIT License. See the LICENSE file for details.