Projeto Final — Matching Engine Completa
Aula 7 de 7
Arquitetura Final
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Cliente │────▶│ Gateway │────▶│ Exchange │
│ (TCP/WS) │ │ (Channel) │ │ (Core) │
└─────────────┘ └──────────────┘ └──────┬───────┘
│
┌───────────────────────────┤
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ OrderBook │ │ Risk Mgr │
│ (AAPL) │ │ (Validação) │
└──────────────┘ └──────────────┘
Classe Exchange — Core do Sistema
public class Exchange
{
private readonly ConcurrentDictionary<string, IOrderBook> _books = new();
private readonly IRiskManager _riskManager;
private readonly ITradePublisher _publisher;
private readonly ILogger _logger;
private readonly IdGenerator _idGen = new();
public Exchange(IRiskManager riskManager, ITradePublisher publisher,
ILogger logger)
{
_riskManager = riskManager;
_publisher = publisher;
_logger = logger;
}
public async Task<EnviarOrdemResultado> EnviarOrdemAsync(Ordem ordem)
{
var stopwatch = Stopwatch.StartNew();
// 1. Validação de risco
if (!_riskManager.Autorizar(ordem))
{
_logger.Warning($"Ordem {ordem.Id} rejeitada pelo risk manager");
return EnviarOrdemResultado.Rejeitada("Risk check falhou");
}
// 2. Obter ou criar OrderBook
var book = _books.GetOrAdd(ordem.Simbolo, _ => new OrderBook());
// 3. Adicionar e executar matches
book.AdicionarOrdem(ordem);
var trades = book.ExecutarMatches();
// 4. Publicar trades
foreach (var trade in trades)
{
await _publisher.PublicarTradeAsync(trade);
_logger.Info($"Trade executado: {trade}");
}
stopwatch.Stop();
_logger.Info($"Ordem {ordem.Id} processada em {stopwatch.ElapsedMicroseconds}µs");
return EnviarOrdemResultado.Sucesso(ordem.Id, trades);
}
public BookSnapshot ObterSnapshot(string simbolo)
=> _books.TryGetValue(simbolo, out var book)
? book.ObterSnapshot()
: BookSnapshot.Vazio(simbolo);
}
Risk Manager
public class RiskManager : IRiskManager
{
private readonly ConcurrentDictionary<string, LimitesCliente> _limites = new();
public bool Autorizar(Ordem ordem)
{
var limites = _limites.GetOrAdd(
ordem.Simbolo, _ => new LimitesCliente());
// Check 1: Quantidade máxima por ordem
if (ordem.Quantidade > 100_000)
return false;
// Check 2: Preço dentro do range (circuit breaker)
if (ordem.Preco.HasValue)
{
var precoAtual = limites.UltimoPreco;
if (precoAtual > 0)
{
var variacao = Math.Abs(ordem.Preco.Value - precoAtual) / precoAtual;
if (variacao > 0.10m) // 10% max
return false;
}
}
// Check 3: Saldo suficiente (modo cash)
if (ordem.Lado == OrdemLado.COMPRA && ordem.Preco.HasValue)
{
var valorTotal = ordem.Quantidade * ordem.Preco.Value;
if (valorTotal > limites.SaldoDisponivel)
return false;
}
return true;
}
}
Market Data Publisher
public interface ITradePublisher
{
Task PublicarTradeAsync(Trade trade);
IAsyncEnumerable<Trade> ObterStream(string simbolo,
CancellationToken ct);
}
public class InMemoryTradePublisher : ITradePublisher
{
private readonly ConcurrentDictionary<string, Channel<Trade>> _channels = new();
public async Task PublicarTradeAsync(Trade trade)
{
if (_channels.TryGetValue(trade.Simbolo, out var channel))
await channel.Writer.WriteAsync(trade);
}
public IAsyncEnumerable<Trade> ObterStream(string simbolo,
CancellationToken ct)
{
var channel = _channels.GetOrAdd(simbolo, _ =>
Channel.CreateUnbounded<Trade>());
return channel.Reader.ReadAllAsync(ct);
}
}
Program.cs — Ponto de Entrada
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IRiskManager, RiskManager>();
builder.Services.AddSingleton<ITradePublisher, InMemoryTradePublisher>();
builder.Services.AddSingleton<ILogger, ConsoleLogger>();
builder.Services.AddSingleton<Exchange>();
builder.Services.AddHostedService<WebSocketServer>();
var app = builder.Build();
app.Run();
Teste de Integração
public class ExchangeIntegrationTests
{
[Fact]
public async Task CompraVenda_DeveExecutarTradeCompleto()
{
var exchange = new Exchange(
new RiskManager(),
new InMemoryTradePublisher(),
new ConsoleLogger());
var compra = new Ordem("AAPL", OrdemLado.COMPRA, 100,
OrdemTipo.LIMITE, 150m);
var venda = new Ordem("AAPL", OrdemLado.VENDA, 100,
OrdemTipo.LIMITE, 150m);
var r1 = await exchange.EnviarOrdemAsync(compra);
var r2 = await exchange.EnviarOrdemAsync(venda);
r1.Trades.Should().HaveCount(1);
r1.Trades[0].Quantidade.Should().Be(100);
r1.Trades[0].Preco.Should().Be(150m);
}
}
Esta Matching Engine implementa price-time priority, risk management, market data streaming e é extensível para WebSocket/TCP. O projeto está pronto para deploy real!