Однажды трейдер заметил, что после публикации макростатистики биржевой поток сделок резко ускорился — 30+ сделок в секунду на BTC/USDT. Стандартный React-интерфейс начал тормозить, пропуская до 40% обновлений. Клиент пришёл к нам с запросом на построение T&S, не теряющего ни одной сделки при пиковых нагрузках. Потери при таких пиках оборачиваются упущенной прибылью: трейдер не видит крупных продаж, опаздывает с входом, а slippage съедает существенную часть сделки. Экономия на slippage может достигать $5000 в месяц для активного трейдера.
Мы разрабатываем T&S-ленты под ключ для криптобирж и проп-трейдингов. 5+ лет опыта и 10+ проектов в crypto позволяют гарантировать стабильность даже при 1000 сделок/сек.
Почему T&S важен для рыночного анализа?
Time & Sales — это пульс рынка. В отличие от свечных графиков, где данные спрессованы в интервалы, T&S показывает каждую сделку: время, цена, объём, агрессивная сторона. Профессиональные трейдеры читают поток как индикатор скрытого давления: серия крупных покупок на уровне поддержки — сигнал к развороту.
Структура ленты сделок
interface Trade {
id: string;
timestamp: number; // unix milliseconds
price: number;
quantity: number;
side: 'buy' | 'sell'; // агрессивная сторона
value: number; // price * quantity в USD
isLargeTrade: boolean; // выше порога значимого объёма
}
Каждая строка T&S содержит:
- Время: HH:MM:SS.mmm (с миллисекундами для профессиональных платформ)
- Цена: с выделением направления изменения
- Объём: в base asset
- Сторона: Buy (зелёный) / Sell (красный)
Какой бэкенд выдерживает 1000 сделок/сек?
Используем Go с кольцевым буфером для O(1) вставки и WebSocket-хабом для стриминга. Новый клиент сразу получает последние 100 сделок из буфера — без лага на заполнение. WebSocket API (см. MDN WebSocket API) обеспечивает двустороннюю связь с минимальным overhead.
Полный код бэкенда
type TimeAndSalesHub struct {
trades chan Trade
clients map[string]map[*WSClient]bool // pair -> clients
mu sync.RWMutex
recentBuf map[string]*RingBuffer // хранит последние N сделок для новых подключений
}
type RingBuffer struct {
items []Trade
head int
size int
mu sync.Mutex
}
func (rb *RingBuffer) Add(trade Trade) {
rb.mu.Lock()
defer rb.mu.Unlock()
rb.items[rb.head%rb.size] = trade
rb.head++
}
func (rb *RingBuffer) GetAll() []Trade {
rb.mu.Lock()
defer rb.mu.Unlock()
result := make([]Trade, 0, rb.size)
start := rb.head - rb.size
if start < 0 { start = 0 }
for i := start; i < rb.head; i++ {
result = append(result, rb.items[i%rb.size])
}
return result
}
func (hub *TimeAndSalesHub) OnTrade(trade Trade) {
hub.recentBuf[trade.Pair].Add(trade)
hub.mu.RLock()
defer hub.mu.RUnlock()
data, _ := json.Marshal(trade)
for client := range hub.clients[trade.Pair] {
select {
case client.send <- data:
default:
go client.close()
}
}
}
func (hub *TimeAndSalesHub) OnClientConnect(client *WSClient, pair string) {
hub.mu.Lock()
hub.clients[pair][client] = true
hub.mu.Unlock()
recent := hub.recentBuf[pair].GetAll()
for _, trade := range recent {
data, _ := json.Marshal(trade)
client.send <- data
}
}
Как добиться 60 FPS на фронтенде?
T&S обновляется очень часто — на BTC/USDT до 10–20 сделок в секунду в активные периоды. Стандартный React список будет тормозить. Прямое DOM manipulation без React re-render даёт 60 FPS при 200 строках.
Код фронтенда с прямым DOM manipulation
import { useRef, useEffect, useCallback } from 'react';
const MAX_ROWS = 200;
function TimeAndSalesList({ pair }: { pair: string }) {
const containerRef = useRef<HTMLDivElement>(null);
const tradesRef = useRef<Trade[]>([]);
const wsRef = useRef<WebSocket | null>(null);
const appendTrade = useCallback((trade: Trade) => {
const container = containerRef.current;
if (!container) return;
const row = document.createElement('div');
row.className = `trade-row ${trade.side} ${trade.isLargeTrade ? 'large' : ''}`;
const time = new Date(trade.timestamp);
const timeStr = `${time.getHours().toString().padStart(2,'0')}:` +
`${time.getMinutes().toString().padStart(2,'0')}:` +
`${time.getSeconds().toString().padStart(2,'0')}`;
row.innerHTML = `
<span class="time">${timeStr}</span>
<span class="price">${formatPrice(trade.price)}</span>
<span class="qty">${formatQuantity(trade.quantity)}</span>
<span class="value">$${formatVolume(trade.value)}</span>
`;
container.insertBefore(row, container.firstChild);
while (container.children.length > MAX_ROWS) {
container.removeChild(container.lastChild!);
}
if (trade.isLargeTrade) {
row.classList.add('flash');
setTimeout(() => row.classList.remove('flash'), 500);
}
}, []);
useEffect(() => {
wsRef.current = new WebSocket(`wss://api.exchange.com/ws`);
wsRef.current.send(JSON.stringify({ op: 'subscribe', channel: `trades.${pair}` }));
wsRef.current.onmessage = (e) => {
const trade = JSON.parse(e.data);
appendTrade(trade);
};
return () => wsRef.current?.close();
}, [pair, appendTrade]);
return (
<div className="time-and-sales">
<div className="ts-header">
<span>Time</span>
<span>Price</span>
<span>Size</span>
<span>Value</span>
</div>
<div ref={containerRef} className="ts-body" />
</div>
);
}
| Подход | FPS при 200 строках | Сложность | Поддержка анимации |
|---|---|---|---|
| React Virtualized | 30–40 | Средняя | Ограничена |
| Прямой DOM manipulation | 60 | Низкая | Полная |
| Canvas | 60+ | Высокая | Тяжело |
Прямое DOM manipulation лучше React Virtualized в 2 раза по FPS при 200 строках — это критично при пиковых нагрузках.
Как настроить фильтры для крупных сделок?
Для фокуса на значимых событиях настраиваем фильтры по минимальному объёму, стороне и порогу крупных блоков. Крупные сделки подсвечиваются иконкой и flash-анимацией, чтобы трейдер не пропустил их в потоке. В коде это реализуется CSS-классами с transition.
Как работает агрегация по времени?
Для менее плотных рынков — объединение сделок за короткий интервал (100–500 мс) снижает частоту обновлений без потери информации. Ниже — пример агрегатора на TypeScript:
class TradeAggregator {
private buffer: Trade[] = [];
private flushInterval: number = 100;
private onFlush: (aggregated: AggregatedTrade[]) => void;
add(trade: Trade) {
this.buffer.push(trade);
}
private flush() {
if (this.buffer.length === 0) return;
const groups = new Map<string, AggregatedTrade>();
for (const trade of this.buffer) {
const key = `${trade.price}:${trade.side}`;
const existing = groups.get(key);
if (existing) {
existing.quantity += trade.quantity;
existing.value += trade.value;
existing.count++;
} else {
groups.set(key, { ...trade, count: 1 });
}
}
this.onFlush([...groups.values()].sort((a, b) => b.timestamp - a.timestamp));
this.buffer = [];
}
}
Статистика потока сделок
Рядом с лентой показываем агрегированную статистику за последние 10 секунд: объём покупок/продаж, дельту, процентное соотношение. Это помогает быстро оценить баланс сил без переключения на другие виджеты.
Процесс работы над T&S-лентой
- Аналитика — обсуждаем источники данных, протоколы WebSocket, требования к производительности.
- Проектирование — создаём схему бэкенда с ring buffer, определяем формат сообщений, фиксируем фильтры.
- Реализация — пишем код на Go и TypeScript, настраиваем прямой DOM manipulation, интегрируем WebSocket.
- Тестирование — нагрузочное тестирование до 1000 сделок/сек, проверка zero-loss, тесты на просадки.
- Деплой — разворачиваем на ваших серверах или в облаке, документируем API.
Ориентировочные сроки: базовая лента готова за 3–4 недели. Стоимость рассчитывается индивидуально — свяжитесь с нами для оценки.
Что входит в разработку
| Компонент | Детали |
|---|---|
| Архитектура | Проектирование бэкенда на Go с кольцевым буфером и WebSocket-хабом |
| Frontend | React-компонент с прямым DOM manipulation, фильтрацией и агрегацией |
| Документация | OpenAPI-схемы, описание WebSocket-протокола, правила настройки |
| Тестирование | Нагрузочное тестирование до 1000 сделок/сек, проверка zero-loss |
| Поддержка | 2 недели бесплатного сопровождения после деплоя |
Заказ разработки
Оценим ваш проект бесплатно — пришлите требования на почту или в Telegram. Получите T&S-ленту под ключ за 3–4 недели с гарантией стабильности. Свяжитесь с нами для консультации и закажите разработку T&S-ленты сегодня.







