Capture to File
The Live capture functions wrap a Live client + a rotating DBN writer + a reconnect loop into one call. Two convenience functions cover the common cases, plus a do-block writer if you want to drive the loop yourself.
Single schema: stream_to_file
The simplest "subscribe and dump every record to disk" call:
using DatabentoAPI
path = stream_to_file(;
schema = Schema.TRADES,
symbols = ["ES.FUT"],
dataset = "GLBX.MDP3",
stype_in = SType.PARENT,
base_dir = "./capture",
duration_s = 600) # stop after 10 minutes; omit for run-forever
@show path # e.g. ./capture/GLBX.MDP3/trades/20260526T143012Z.dbn.zstOutput is a zstd-compressed DBN file by default. The path is {base_dir}/{dataset}/{schema}/{utc-yyyymmddTHHMMSSZ}.dbn.zst — see default_live_path for the format.
Press Ctrl-C for a clean shutdown — the active zstd frame footer is flushed before the function returns.
Multiple schemas on one TCP connection: stream_multi_to_files
paths = stream_multi_to_files(;
schemas = [Schema.TCBBO, Schema.CBBO_1S, Schema.STATUS],
symbols = ["SPXW.OPT"],
dataset = "OPRA.PILLAR",
stype_in = SType.PARENT,
base_dir = "./capture",
duration_s = 60)
@show paths # Dict{Schema.T,String} — one file per schemaOne Live client serves N schemas. The gateway dedupes SymbolMappingMsg within a connection (~50% fewer mapping records than per-schema clients); each schema's records route to its own .dbn.zst.
In-file zstd frame rotation (frame_seconds)
Both functions accept frame_seconds (default 60.0). Every minute, the active zstd frame's footer is written and a fresh frame begins on the same file. Multi-frame .dbn.zst is standards-compliant — any zstd reader concatenates frames transparently — so a hard kill (SIGKILL, OOM, power loss) loses at most one frame of records instead of corrupting the whole file. Pass frame_seconds = nothing to write a single frame.
A quiet schema (e.g. STATUS) still has its zstd buffer flushed by the session monitor on the same cadence, so even with no records flowing the on-disk file stays current.
Time-based file rotation (rotate_seconds)
Roll to a new output file at a fixed cadence:
stream_to_file(...; rotate_seconds = 3600) # new file every hourThe previous file is closed cleanly (frame footer written) before the next opens. Combine with frame_seconds for both crash safety and time-bucketed files.
Custom loops: open_dbn_writer + write_record!
For workflows where stream_to_file is too opinionated — e.g. you want to filter records, dispatch to multiple writers, or write from a custom data source — pair Live with open_dbn_writer:
Live(dataset = "GLBX.MDP3") do live
connect!(live)
subscribe!(live; schema = Schema.TRADES, symbols = ["ES.FUT"], stype_in = SType.PARENT)
start!(live)
open_dbn_writer(;
base_dir = "./capture", dataset = "GLBX.MDP3",
schema = Schema.TRADES, symbols = ["ES.FUT"], stype_in = SType.PARENT,
frame_seconds = 60.0) do writer
for rec in live
rec isa DBN.TradeMsg && rec.size > 100 || continue
write_record!(writer, rec)
end
end
endThe writer's current_path field exposes the most recently opened output path. The do-block's finally closes the writer (frame footer + zstd footer emitted in order).
Reconnect semantics
reconnect = true (default) on stream_to_file / stream_multi_to_files re-establishes the TCP session after a drop using full-jitter exponential backoff (1s base, 60s cap):
sleep = rand() * min(cap, base * 2^(attempt-1))After max_reconnect_attempts retries (default 10) the loop gives up and returns the path it last wrote. Pass nothing for unlimited retries.
On reconnect, each schema re-subscribes with start = the lowest per-instrument timestamp seen so far (tsrecv for BBO families, tsevent otherwise), bounded to Databento's 24-hour replay window. Gaps shorter than the disconnect duration get filled automatically.
Decoding captured files
metadata, records = read_capture("capture/GLBX.MDP3/trades/20260526T143012Z.dbn.zst")Returns the DBN metadata header plus the full record vector. Thin wrapper around DBN.read_dbn_with_metadata re-exported here so you don't need to using DatabentoBinaryEncoding.
For very large captures, decode with DBN.jl's typed callback API directly:
import DatabentoBinaryEncoding as DBN
DBN.foreach_record("capture/.../big.dbn.zst", DBN.TradeMsg) do trade
# … process each trade …
endWhat ends up in the file?
Captured .dbn.zst files contain:
- All data records for the subscribed schema(s).
SymbolMappingMsgrecords (interleaved with data records, in arrival order) so downstream tooling can resolveinstrument_id→raw_symbolwithout a separateresolvecall.
SystemMsg / ErrorMsg records are counted internally for the session log but not written to disk — they're heartbeat / control noise that doesn't belong in a record stream.