Streaming
O SDK expõe dois streams de longa duração:
- Stream de snapshots — atualizações de algos enviadas pela engine.
- Stream de market data — atualizações de book e trades por
papel, além dos feeds brutos do mdhub da B3 (
trd,xtrd,mbp,mbo, …) retransmitidos em/mdh; veja Feeds mdhub.
Ambos são async iterators com reconexão automática, replay de
assinaturas e um sentinela StreamReconnected após cada reconexão
bem-sucedida, para que o consumidor possa re-inicializar seu estado.
Stream de snapshots
Assine todos os seus algos rodando (ou um subconjunto filtrado) e reaja conforme a engine envia atualizações.
from investflex.ws.stream import StreamReconnected
from investflex.models.snapshot import StrategySnapshot
async with AsyncInvestflexClient.from_env() as client:
async with client.algos.stream() as stream:
await stream.subscribe_all()
async for evt in stream:
if isinstance(evt, StreamReconnected):
# A conexão caiu e voltou. Reinicialize se você
# mantém um cache local.
continue
if isinstance(evt, StrategySnapshot):
print(evt.clord_id, evt.status.name,
evt.output.cum_qty_perc, "%")
import { StreamReconnected } from "@investflex/sdk";
const client = InvestflexClient.fromEnv();
const stream = await client.algos.stream().open();
await stream.subscribeAll();
for await (const evt of stream) {
if (evt instanceof StreamReconnected) {
// A conexão caiu e voltou. Ressemeie se você
// mantém um cache local.
continue;
}
console.log(evt.clOrdId, evt.status, evt.output.cumQtyPerc, "%");
}
await stream.close();
Se você prefere um dict gerenciado em vez de consumir eventos na mão, veja Snapshots → Live view.
Backpressure
Os construtores de stream aceitam um modo backpressure:
"block"(padrão) — consumidores lentos seguram o produtor."drop_oldest"— limita a fila descartando eventos antigos.
client.algos.stream(queue_size=2048, backpressure="drop_oldest")
client.algos.stream({ queueSize: 2048, backpressure: "drop_oldest" });
Stream de market data
O stream de market data é por canal: você assina um canal book ou
trades para um símbolo de cada vez. Assinaturas são contadas por
referência, então assinar o mesmo par (canal, símbolo) duas vezes
gera apenas uma assinatura upstream.
from investflex.models.marketdata import Book, Trade
async with AsyncInvestflexClient.from_env() as client:
async with client.marketdata.stream() as stream:
await stream.subscribe_book("PETR4")
await stream.subscribe_trades("PETR4")
async for evt in stream:
channel = evt.get("channel")
data = evt.get("data") or {}
if channel == "book":
book = Book.model_validate(data)
if book.bids and book.asks:
print("toque", book.bids[0].price, "/", book.asks[0].price)
elif channel == "trades":
trade = Trade.model_validate(data)
print("trade", trade.price, "x", trade.qty)
const client = InvestflexClient.fromEnv();
const stream = await client.marketdata.stream().open();
await stream.subscribeBook("PETR4");
await stream.subscribeTrades("PETR4");
for await (const evt of stream) {
const data = evt.data ?? {};
if (evt.channel === "book") {
const book = data as { bids?: { price: number }[]; asks?: { price: number }[] };
if (book.bids?.length && book.asks?.length) {
console.log("touch", book.bids[0]!.price, "/", book.asks[0]!.price);
}
} else if (evt.channel === "trades") {
const trade = data as { price?: number | null; qty?: number | null };
console.log("trade", trade.price, "x", trade.qty);
}
}
await stream.close();
Para conveniência, MarketDataService.parse(stream) faz a
classificação + model_validate e devolve Book, Trade ou
Definition tipados:
from investflex.services.marketdata import MarketDataService
from investflex.models.marketdata import Book, Trade
async with AsyncInvestflexClient.from_env() as client:
async with client.marketdata.stream() as stream:
await stream.subscribe_book("PETR4")
await stream.subscribe_trades("PETR4")
async for evt in MarketDataService.parse(stream):
if isinstance(evt, Book):
print("book", len(evt.bids), "/", len(evt.asks))
elif isinstance(evt, Trade):
print("trade", evt.price)
import { MarketDataService, type Book, type Trade } from "@investflex/sdk";
const client = InvestflexClient.fromEnv();
const stream = await client.marketdata.stream().open();
await stream.subscribeBook("PETR4");
await stream.subscribeTrades("PETR4");
for await (const evt of MarketDataService.parse(stream)) {
const book = evt as Partial<Book>;
const trade = evt as Partial<Trade>;
if (book.bids && book.asks) {
console.log("book", book.bids.length, "/", book.asks.length);
} else if (trade.price != null) {
console.log("trade", trade.price);
}
}
await stream.close();
Book
Book é a visão MBP agregada de um papel:
| Campo | Tipo | Observações |
|---|---|---|
symbol |
str | |
bids |
list de BookLevel |
Ordenado do melhor ao pior. |
asks |
list de BookLevel |
Ordenado do melhor ao pior. |
timestamp |
datetime | None |
BookLevel: price, qty, orders.
Minibooks de opções agrupados
O canal de minibook de opções faz uma única assinatura para todos os contratos
de uma combinação ativo-objeto/vencimento. A data aceita YYYY-MM-DD ou
YYYYMMDD. Os frames usam channel == "option_books"; os metadados de rota
ficam ao lado de data, e campos ausentes não devem apagar o último book/trade.
Faça o merge por símbolo.
await stream.subscribe_option_books("VALE3", "2026-09-04")
# ... consuma frames option_books ...
await stream.unsubscribe_option_books("VALE3", "2026-09-04")
await stream.subscribeOptionBooks("VALE3", "2026-09-04");
stream.on("option_books", (frame) => {
console.log(frame.underlying, frame.maturityDate, frame.data);
});
await stream.unsubscribeOptionBooks("VALE3", "2026-09-04");
O destino é /topic/minibook/{market}/{underlying}/{securityType}/{YYYYMMDD};
securityType usa OPT por padrão. Após reaplicar as assinaturas, o stream
TypeScript emite reconnect com o canal afetado para permitir um novo seed REST.
Trades detalhados
Por padrão os frames de trade trazem symbol, price, qty,
trade_id e timestamp. Passe detailed=True na assinatura e cada
evento de trade daquele papel passa a carregar também os campos extras
do venue:
await stream.subscribe_trades("PETR4", detailed=True)
await stream.subscribeTrades("PETR4", { detailed: true });
| Campo | Tipo | Notas |
|---|---|---|
buyer_id / seller_id |
str | None | Códigos das corretoras. |
vwap |
float | None | VWAP do pregão. |
trd_vol |
int | None | Quantidade acumulada negociada. |
fin_vol |
float | None | Volume financeiro acumulado. |
var |
float | None | Variação sobre o preço de referência. |
band_max_price / band_min_price |
float | None | Bandas de leilão. |
quantity_band_limit |
int | None | Banda de quantidade do leilão. |
open_int |
int | None | Contratos em aberto. |
open_px / close_px / high_px / low_px |
float | None | Preços do pregão. |
trade_nbr |
int | None | Número sequencial do negócio. |
A opção vale por papel e persiste enquanto a assinatura existir: o
stream tem uma única fila de eventos, então basta um assinante pedir
detalhe para que todos os consumidores daquele papel vejam os campos
extras. Ela é limpa quando o último assinante do papel cancela a
assinatura — reassine com detailed=True se ainda os quiser.
Cancelando assinatura
await stream.unsubscribe(channel="trades", symbol="PETR4")
await stream.unsubscribe({ channel: "trades", symbol: "PETR4" });
Unsubscribes também são contados por referência; a assinatura upstream permanece enquanto outro consumidor a mantiver.
Feeds mdhub
O mesmo objeto de stream também transporta os feeds mdhub da B3
vindos do broadcaster /mdh: negócios, books por ordem e agregados,
resumos de execução, estatísticas, fases de negociação e mais. Cada
feed tem um método com o seu nome de wire (subscribe_trd,
subscribe_mbp, subscribe_mbo, …; subscribeTrd, subscribeMbp,
subscribeMbo, … em TypeScript). Os eventos chegam com o nome do feed
em channel e o payload da bolsa repassado sem alterações:
await stream.subscribe_xtrd("PETR4")
await stream.subscribe_mbp("PETR4")
async for evt in stream:
if evt["channel"] == "xtrd":
print("trade", evt["data"]["price"], "vwap", evt["data"]["vwapPrice"])
await stream.subscribeXtrd("PETR4");
await stream.subscribeMbp("PETR4");
stream.on("xtrd", (frame) => console.log("trade", frame.data.price, "vwap", frame.data.vwapPrice));
A lista completa de feeds, o formato dos eventos, os modelos e as peculiaridades do wire estão especificados em Feeds mdhub.
Semântica de reconexão
Ambos os streams reconectam automaticamente em falhas transientes de rede, com backoff exponencial. Após uma reconexão bem-sucedida:
- Assinaturas pré-existentes são reaplicadas no upstream.
- Um
StreamReconnected(attempt=N)é emitido no fluxo de eventos para você re-inicializar estado local (ex.: listando os algos novamente, ou limpando uma escada de livro).
Se você quer essa re-inicialização tratada automaticamente, o helper live view faz isso de forma transparente.
Limpeza
Streams são async context managers — entrar no async with
conecta, sair derruba o WebSocket. Não gerencie a conexão na mão.