Разработка криптобиржи (CEX)
Централизованная криптобиржа — это самый сложный продукт в крипто-инфраструктуре. Matching engine, кастодиальная система, compliance слой, market data, ликвидность — всё это должно работать одновременно, надёжно и под нагрузкой. Расскажу как это устроено изнутри и что реально нужно для запуска серьёзной платформы.
Системная архитектура
Высокоуровневая схема
Client Layer
├── Web Trading Terminal (React/Vue)
├── Mobile Apps (iOS/Android)
└── API (REST + WebSocket + FIX)
│
API Gateway / Load Balancer
│
Service Layer
├── Auth Service (JWT + 2FA)
├── Order Service → OMS
├── Account Service → Balances
├── Market Data Service → Feeds
└── Notification Service
│
Core Infrastructure
├── Matching Engine (C++/Rust/Go)
├── Risk Engine
├── Custody System
└── Settlement Engine
│
Data Layer
├── PostgreSQL (accounts, orders, trades)
├── Redis (order book state, sessions)
├── Kafka (event streaming, audit log)
└── TimescaleDB (OHLCV, market data)
Принцип изоляции сервисов
Каждый сервис независим и общается через события (event-driven). Matching engine не знает про пользователей — он знает только про ордера. Account service не знает про торговлю — он знает про балансы.
Это важно по нескольким причинам:
- Независимое масштабирование: matching engine на отдельном bare metal, всё остальное на Kubernetes
- Fault isolation: падение notification service не затрагивает trading
- Независимый деплой без downtime
Matching Engine
Требования к производительности
Matching engine — единственный компонент, где производительность имеет экзистенциальное значение. Задержка >10ms — это реальные потери для HFT клиентов и market makers.
| Tier | Throughput | Latency (order-to-ack) | Технология |
|---|---|---|---|
| Startup (<$1M/day) | 1,000 ops/sec | <50ms | Go / Java |
| Mid-tier ($1-50M/day) | 10,000 ops/sec | <5ms | Go / Rust |
| Large ($50M+/day) | 100,000+ ops/sec | <500µs | C++ / Rust |
Order Book имплементация
use std::collections::BTreeMap;
use std::collections::VecDeque;
type Price = u64; // Integer representation: price * 10^8
type Quantity = u64;
#[derive(Debug, Clone)]
struct Order {
id: u64,
price: Price,
quantity: Quantity,
remaining: Quantity,
side: Side,
order_type: OrderType,
timestamp: u64,
user_id: u64,
}
#[derive(Debug)]
struct PriceLevel {
price: Price,
total_quantity: Quantity,
orders: VecDeque<u64>, // order IDs в порядке времени
}
struct OrderBook {
symbol: String,
bids: BTreeMap<std::cmp::Reverse<Price>, PriceLevel>,
asks: BTreeMap<Price, PriceLevel>,
orders: std::collections::HashMap<u64, Order>,
}
impl OrderBook {
fn match_order(&mut self, incoming: &mut Order) -> Vec<Trade> {
let mut trades = Vec::new();
let opposite_side = match incoming.side {
Side::Buy => &mut self.asks,
Side::Sell => &mut self.bids,
};
// Пока есть ликвидность и ордер не заполнен
while incoming.remaining > 0 {
let best_level = match incoming.side {
Side::Buy => opposite_side.iter_mut().next(),
Side::Sell => opposite_side.iter_mut().next(),
};
match best_level {
None => break, // Нет противоположных ордеров
Some((level_price, level)) => {
// Проверяем совпадение цен
let price_matches = match incoming.side {
Side::Buy => incoming.price >= level.price,
Side::Sell => incoming.price <= level.price,
};
if !price_matches {
break;
}
// Исполняем на данном уровне
while incoming.remaining > 0 && !level.orders.is_empty() {
let maker_id = *level.orders.front().unwrap();
let maker = self.orders.get_mut(&maker_id).unwrap();
let fill_qty = incoming.remaining.min(maker.remaining);
let fill_price = maker.price; // Maker's price
trades.push(Trade {
maker_order_id: maker_id,
taker_order_id: incoming.id,
price: fill_price,
quantity: fill_qty,
});
incoming.remaining -= fill_qty;
maker.remaining -= fill_qty;
if maker.remaining == 0 {
level.orders.pop_front();
self.orders.remove(&maker_id);
}
}
}
}
}
trades
}
}
Event sourcing для matching engine
Matching engine использует event sourcing — все изменения состояния записываются как неизменяемая последовательность событий. Восстановление состояния = replay событий с начала (или с последнего snapshot).
OrderPlaced → OrderMatched → TradeFilled → OrderCancelled → ...
Преимущества: полный audit trail, возможность восстановления после краша, воспроизведение торгов для анализа.
Custody система
Кастодиальная архитектура
Биржа хранит средства пользователей. Это огромная ответственность: история знает десятки взломов крупных бирж (Mt.Gox, Bitfinex, FTX). Правильная архитектура хранения — не опциональная фича, а базовое требование.
Multi-sig cold storage:
- 95%+ средств в cold wallets
- Подписание транзакций на air-gapped machines
- 3-of-5 или 5-of-7 multisig схема
- Физически разнесённые ключи (разные дата-центры, страны)
- Hardware Security Modules (HSM) для ключевых хранителей
Hot wallet:
- 2-5% средств для оперативных выводов
- Автоматическое пополнение из cold при снижении баланса ниже порога
- Daily withdrawal limit: если превышен — ручное одобрение
Withdrawal security:
class WithdrawalProcessor:
async def process_withdrawal(self, request: WithdrawalRequest) -> bool:
# 1. Verify user ownership
user = await self.db.get_user(request.user_id)
if not user.is_withdrawal_address_whitelisted(request.address):
raise SecurityError("Address not whitelisted")
# 2. Check 24h velocity
daily_withdrawn = await self.db.get_daily_withdrawn(request.user_id)
if daily_withdrawn + request.amount > user.daily_withdrawal_limit:
raise LimitError("Daily limit exceeded")
# 3. AML screening
risk_score = await self.aml_provider.screen_address(request.address)
if risk_score > 70:
await self.flag_for_manual_review(request)
return False
# 4. Reserve funds atomically
async with self.db.transaction():
await self.db.debit_balance(request.user_id, request.amount)
await self.db.create_pending_withdrawal(request)
# 5. Queue for hot wallet signing
await self.hot_wallet_queue.put(request)
return True
KYC/AML и compliance
Уровни верификации
| KYC Level | Данные | Дневной лимит |
|---|---|---|
| 0 — Email | Только email | 0 (только просмотр) |
| 1 — Basic | Телефон + страна | $500 |
| 2 — Standard | ID + Selfie | $10,000 |
| 3 — Enhanced | Proof of address + source of funds | $100,000 |
| Institutional | Корпоративные документы | Unlimited |
Интеграция с Sumsub или Onfido через REST API. Webhook при изменении статуса верификации.
Transaction Monitoring
AML скрининг каждого депозита и вывода:
async def screen_transaction(self, address: str, amount_usd: float) -> RiskResult:
# Chainlink + Elliptic API
result = await self.chainalysis.check_address(address)
risk_factors = []
if result.is_sanctioned: # OFAC SDN list
return RiskResult(action='block', reason='sanctioned_entity')
if result.exposure.darknet_market > 10: # >10% от объёма с darknet
risk_factors.append('darknet_exposure')
if result.exposure.stolen_funds > 5:
risk_factors.append('stolen_funds')
if amount_usd > 10_000: # CTR threshold
risk_factors.append('large_transaction')
if risk_factors:
await self.submit_sar_if_needed(address, amount_usd, risk_factors)
risk_score = self.calculate_risk_score(result, risk_factors)
return RiskResult(
action='allow' if risk_score < 70 else 'review',
score=risk_score,
factors=risk_factors
)
Market Data система
Real-time feeds
Market data — это то, что видит трейдер: стакан, свечи, last price, volume. При тысячах подписчиков это серьёзная нагрузка.
Архитектура:
Matching Engine → Trade Events → Kafka
│
┌──────────┤
▼ ▼
OHLCV Builder Order Book Aggregator
│ │
└────┬─────┘
▼
WebSocket Broadcaster
(горизонтальное масштабирование)
│
┌─────────┼─────────┐
▼ ▼ ▼
Client 1 Client 2 Client N
WebSocket сервер публикует diff updates (delta, не полный стакан при каждом изменении). Клиент держит локальную копию стакана и применяет дельты — это снижает трафик в 10-50 раз.
TradingView Advanced Charts интеграция
Профессиональный trading terminal требует TradingView Advanced Charts (платная лицензия). Биржа имплементирует UDF (Unified Data Format) data feed:
// UDF API endpoints
GET /history?symbol=BTCUSDT&resolution=60&from=1700000000&to=1700100000
GET /symbols?symbol=BTCUSDT
GET /search?query=BTC&limit=30
Real-time через WebSocket: при каждом закрытии свечи или новом трейде биржа пушит обновление через TradingView Streaming API.
Операционные вопросы
Liquidity bootstrap
Биржа без ликвидности мертва. Варианты для старта:
- Aggregated liquidity: подключить Binance/OKX как liquidity source через market making бота — бот выставляет внутренние ордера, хеджируя на внешней бирже
- Designated Market Makers: соглашение с профессиональными MM компаниями (Jump, Wintermute, GSR) — они получают льготы (fee rebates, access to API), предоставляют ликвидность
- Liquidity mining: пользователи, добавляющие liquidity через limit orders, получают токен биржи
Инфраструктура и масштабирование
| Компонент | Инфраструктура | Стоимость/мес |
|---|---|---|
| Matching Engine | Bare metal (32-core, 128GB RAM) | $2,000-5,000 |
| API servers | Kubernetes (10-50 nodes) | $5,000-20,000 |
| Databases | Managed PostgreSQL cluster | $1,000-5,000 |
| CDN + DDoS protection | Cloudflare Enterprise | $2,000-10,000 |
| HSM (cold storage) | AWS CloudHSM | $1,500/мес |
Минимальный бюджет на инфраструктуру: $15,000-50,000/мес для серьёзной биржи. Плюс команда: минимум 8-12 инженеров. Срок разработки с нуля: 12-24 месяца.







