One day a trader noticed that after macroeconomic data releases, the exchange trade flow accelerated sharply — 30+ trades per second on BTC/USDT. The standard React interface started lagging, missing up to 40% of updates. The client came to us with a request to build a T&S feed that would not lose a single trade under peak loads. Losses during such spikes translate into missed profit: the trader doesn't see large sells, enters positions late, and slippage eats a significant portion of the trade. Slippage savings can reach $5000 per month for an active trader.
We develop turnkey T&S feeds for crypto exchanges and prop trading firms. With 5+ years of experience and 10+ projects in crypto, we guarantee stability even at 1000 trades/sec.
Why T&S is important for market analysis
Time & Sales is the pulse of the market. Unlike candlestick charts, where data is compressed into intervals, T&S shows every trade: time, price, volume, and aggressive side. Professional traders read the flow as an indicator of hidden pressure: a series of large purchases at a support level signals a reversal.
Trade feed structure
interface Trade {
id: string;
timestamp: number; // unix milliseconds
price: number;
quantity: number;
side: 'buy' | 'sell'; // aggressive side
value: number; // price * quantity in USD
isLargeTrade: boolean; // above significant volume threshold
}
Each T&S row contains:
- Time: HH:MM:SS.mmm (with milliseconds for professional platforms)
- Price: with direction change highlight
- Volume: in base asset
- Side: Buy (green) / Sell (red)
What backend can handle 1000 trades/sec?
We use Go with a ring buffer for O(1) insertion and a WebSocket hub for streaming. A new client immediately receives the last 100 trades from the buffer — no lag filling. The WebSocket API (see MDN WebSocket API) provides bidirectional communication with minimal overhead.
Full backend code
type TimeAndSalesHub struct {
trades chan Trade
clients map[string]map[*WSClient]bool // pair -> clients
mu sync.RWMutex
recentBuf map[string]*RingBuffer // stores last N trades for new connections
}
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
}
}
How to achieve 60 FPS on the frontend?
T&S updates very frequently — on BTC/USDT up to 10–20 trades per second during active periods. A standard React list will lag. Direct DOM manipulation without React re-render gives 60 FPS at 200 rows.
Frontend code with direct 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>
);
}
| Approach | FPS at 200 rows | Complexity | Animation support |
|---|---|---|---|
| React Virtualized | 30–40 | Medium | Limited |
| Direct DOM manipulation | 60 | Low | Full |
| Canvas | 60+ | High | Difficult |
Direct DOM manipulation outperforms React Virtualized by 2x in FPS at 200 rows — critical during peak loads.
How to set up filters for large trades?
To focus on significant events, we configure filters by minimum volume, side, and large block threshold. Large trades are highlighted with an icon and flash animation so the trader doesn't miss them in the stream. This is implemented via CSS classes with transitions.
How time aggregation works
For less dense markets, combining trades over a short interval (100–500 ms) reduces update frequency without losing information. Below is an example aggregator in 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 = [];
}
}
Trade flow statistics
Alongside the feed, we display aggregated statistics for the last 10 seconds: buy/sell volume, delta, and percentage ratio. This helps quickly assess the balance of power without switching to other widgets.
Process for developing a T&S feed
- Analytics — discuss data sources, WebSocket protocols, and performance requirements.
- Design — create a backend schema with ring buffer, define message format, and finalize filters.
- Implementation — write Go and TypeScript code, set up direct DOM manipulation, and integrate WebSocket.
- Testing — load testing up to 1000 trades/sec, zero-loss verification, and drawdown tests.
- Deployment — deploy on your servers or in the cloud, and document the API.
Estimated timeline: basic feed ready in 3–4 weeks. Pricing is determined individually — contact us for an estimate.
What's included in development
| Component | Details |
|---|---|
| Architecture | Backend design in Go with ring buffer and WebSocket hub |
| Frontend | React component with direct DOM manipulation, filtering, and aggregation |
| Documentation | OpenAPI schemas, WebSocket protocol description, configuration guidelines |
| Testing | Load testing up to 1000 trades/sec, zero-loss verification |
| Support | 2 weeks of free post-deployment support |
Order development
We'll evaluate your project for free — send your requirements via email or Telegram. Get a turnkey T&S feed in 3–4 weeks with a stability guarantee. Contact us for a consultation and order the development of your T&S feed today.







