Order Book — Price-Time Priority
Aula 3 de 7
Estrutura do Order Book
O coração de uma Matching Engine é o Order Book — uma estrutura que mantém ordens de compra e venda organizadas por preço e tempo.
BIDS (COMPRAS) ASKS (VENDAS)
Preço Qtd Hora Preço Qtd Hora
───── ─── ──── ───── ─── ────
15.10 200 09:01 15.20 150 09:00
15.05 100 09:02 15.25 300 09:01
15.00 500 09:00 15.30 100 09:03
- Bids (compras): ordenados por preço decrescente (maior oferta primeiro)
- Asks (vendas): ordenados por preço crescente (menor oferta primeiro)
- Price-Time Priority: mesma faixa de preço, ordem mais antiga primeiro (FIFO)
Implementação
public class OrderBook
{
private readonly string _simbolo;
// Bids: preço decrescente → usar comparador reverso
private readonly SortedDictionary<decimal, SortedSet<OrdemEntry>> _bids = new(
Comparer<decimal>.Create((a, b) => b.CompareTo(a))
);
// Asks: preço crescente (padrão)
private readonly SortedDictionary<decimal, SortedSet<OrdemEntry>> _asks = new();
private readonly object _lock = new();
public OrderBook(string simbolo)
{
_simbolo = simbolo;
}
public void AdicionarOrdem(Ordem ordem)
{
var book = ordem.Lado == OrdemLado.COMPRA ? _bids : _asks;
var entry = new OrdemEntry(ordem.Id, ordem.QuantidadeRestante, ordem.CriadaEm);
lock (_lock)
{
if (!book.TryGetValue(ordem.Preco!.Value, out var levels))
{
levels = new SortedSet<OrdemEntry>();
book[ordem.Preco.Value] = levels;
}
levels.Add(entry);
}
}
public bool RemoverOrdem(Guid ordemId, OrdemLado lado, decimal preco)
{
var book = lado == OrdemLado.COMPRA ? _bids : _asks;
lock (_lock)
{
if (!book.TryGetValue(preco, out var levels))
return false;
var entry = levels.FirstOrDefault(e => e.OrdemId == ordemId);
if (entry == null) return false;
levels.Remove(entry);
if (levels.Count == 0)
book.Remove(preco);
return true;
}
}
}
internal record OrdemEntry(Guid OrdemId, int QuantidadeRestante, DateTime CriadaEm)
: IComparable<OrdemEntry>
{
public int CompareTo(OrdemEntry? other)
{
if (other == null) return 1;
// FIFO: mais antiga primeiro
int cmp = CriadaEm.CompareTo(other.CriadaEm);
return cmp != 0 ? cmp : OrdemId.CompareTo(other.OrdemId);
}
}
Matching Logic
public List<Trade> ExecutarMatches()
{
var trades = new List<Trade>();
lock (_lock)
{
while (_bids.Count > 0 && _asks.Count > 0)
{
// Melhor bid (maior preço) e melhor ask (menor preço)
var bestBid = _bids.First();
var bestAsk = _asks.First();
if (bestBid.Key < bestAsk.Key)
break; // spread positivo, sem match
var buyEntry = bestBid.Value.First();
var sellEntry = bestAsk.Value.First();
var matchPreco = bestAsk.Key; // preço do vendedor (ou midpoint)
var matchQtd = Math.Min(buyEntry.QuantidadeRestante, sellEntry.QuantidadeRestante);
var trade = new Trade(
Guid.NewGuid(),
buyEntry.OrdemId, sellEntry.OrdemId,
_simbolo, matchQtd, matchPreco,
DateTime.UtcNow
);
trades.Add(trade);
AtualizarOrdem(bestBid, buyEntry, matchQtd);
AtualizarOrdem(bestAsk, sellEntry, matchQtd);
}
}
return trades;
}
O Order Book com price-time priority é o padrão Nasdaq e a maioria das bolsas globais. Prioriza o melhor preço; em caso de empate, quem chegou primeiro.