ZenQuant.Recorder.JSONL (zen_quant v0.2.0)

Copy Markdown View Source

JSONL snapshot recorder for trading data.

Writes timestamped snapshots (tickers, OI, greeks, walls) as one JSON object per line for replay and backtesting analysis.

Entry Format

{"ts":"2026-02-23T12:00:00Z","type":"ticker","data":{"symbol":"BTC/USDT","last":95000.5}}
{"ts":"2026-02-23T12:00:00.123456Z","type":"greeks","data":{"delta":0.55}}
  • ts — ISO8601 timestamp
  • type — optional snapshot label (omitted when not provided)
  • data — the payload (any JSON-serializable term)

Usage

# Write snapshots
JSONL.append("/tmp/session.jsonl", %{symbol: "BTC/USDT", last: 95000.5}, type: :ticker)
JSONL.append("/tmp/session.jsonl", %{delta: 0.55}, type: :greeks)

# Read back
{:ok, entries} = JSONL.read_all("/tmp/session.jsonl")

# Stream lazily
"/tmp/session.jsonl"
|> JSONL.stream()
|> Stream.filter(&match?({:ok, _}, &1))
|> Enum.each(fn {:ok, entry} -> process(entry) end)

# File metadata
{:ok, info} = JSONL.info("/tmp/session.jsonl")
# => %{count: 2, first_ts: ~U[...], last_ts: ~U[...], duration_ms: 123, types: %{"ticker" => 1, "greeks" => 1}}

API Functions

FunctionArityDescriptionParam Kinds
info1Return metadata about a JSONL file (count, timestamps, duration, type frequencies).path: value
read_all1Read all valid entries from a JSONL file (skips malformed lines).path: value
stream1Stream entries lazily from a JSONL file.path: value
write_many2Write multiple entries to a JSONL file in a single operation.path: value, entries: value
append3Append a single entry to a JSONL file.path: value, data: value
decode_entry1Decode a JSONL line string into an entry map with atom keys.line: value
encode_entry2Encode a data payload into a JSONL line string.data: value

Summary

Functions

Appends a single entry to a JSONL file.

Decodes a JSONL line string into an entry map with atom keys.

Encodes a data payload into a JSONL line string.

Returns metadata about a JSONL file.

Reads all valid entries from a JSONL file.

Streams entries lazily from a JSONL file.

Writes multiple entries to a JSONL file.

Functions

append(path, data, opts \\ [])

@spec append(Path.t(), term(), keyword()) :: :ok | {:error, term()}

Appends a single entry to a JSONL file.

Creates the file and parent directories if they don't exist.

Options

Same as encode_entry/2: type: and ts:.

Examples

:ok = JSONL.append("/tmp/session.jsonl", %{last: 95000.5}, type: :ticker)

decode_entry(line)

@spec decode_entry(String.t()) :: {:ok, map()} | {:error, term()}

Decodes a JSONL line string into an entry map with atom keys.

Known keys (:ts, :type, :data) are converted to atoms. The :ts value is parsed back to a DateTime.

Examples

{:ok, entry} = JSONL.decode_entry(~s({"ts":"2026-02-23T12:00:00Z","type":"ticker","data":{"last":95000.5}}))
entry.type
# => "ticker"
entry.ts
# => ~U[2026-02-23 12:00:00Z]

encode_entry(data, opts \\ [])

@spec encode_entry(
  term(),
  keyword()
) :: String.t()

Encodes a data payload into a JSONL line string.

Returns a single-line JSON string (no trailing newline).

Options

  • type: — atom label (:ticker, :orderbook, etc.), converted to string
  • ts: — DateTime override (default: DateTime.utc_now())

Examples

entry = JSONL.encode_entry(%{symbol: "BTC/USDT", last: 95000.5}, type: :ticker)
# => ~s({"ts":"2026-02-23T12:00:00Z","type":"ticker","data":{"symbol":"BTC/USDT","last":95000.5}})

entry = JSONL.encode_entry(%{delta: 0.55})
# => ~s({"ts":"...","data":{"delta":0.55}})

info(path)

@spec info(Path.t()) :: {:ok, map()} | {:error, term()}

Returns metadata about a JSONL file.

Return Value

%{
  count: integer(),
  first_ts: DateTime.t() | nil,
  last_ts: DateTime.t() | nil,
  duration_ms: integer(),
  types: %{String.t() => pos_integer()}
}

Examples

{:ok, info} = JSONL.info("/tmp/session.jsonl")
info.count
# => 150
info.types
# => %{"ticker" => 100, "greeks" => 50}

read_all(path)

@spec read_all(Path.t()) :: {:ok, [map()]} | {:error, term()}

Reads all valid entries from a JSONL file.

Malformed lines are skipped silently. Returns only successfully decoded entries. For strict parsing, use stream/1 and handle errors explicitly.

Examples

{:ok, entries} = JSONL.read_all("/tmp/session.jsonl")

stream(path)

@spec stream(Path.t()) :: Enumerable.t()

Streams entries lazily from a JSONL file.

Each element is {:ok, entry_map} or {:error, reason}. Raises File.Error if the file doesn't exist (streaming a nonexistent file is a programming error).

Blank lines are skipped. Malformed lines yield {:error, reason}.

Examples

"/tmp/session.jsonl"
|> JSONL.stream()
|> Stream.filter(&match?({:ok, _}, &1))
|> Enum.each(fn {:ok, entry} -> process(entry) end)

write_many(path, entries)

@spec write_many(Path.t(), [{term(), keyword()}]) :: :ok | {:error, term()}

Writes multiple entries to a JSONL file.

Creates the file and parent directories if they don't exist. Each entry is a tuple of {data, opts} matching encode_entry/2 args.

Examples

entries = [
  {%{last: 95000.5}, type: :ticker},
  {%{delta: 0.55}, type: :greeks}
]
:ok = JSONL.write_many("/tmp/session.jsonl", entries)