Skip to content
Join the waitlistWaitlist

The journal

The risk engine's append-only, checksum-chained journal, what each line holds, when a line is written, and what it can and cannot detect.

The risk engine remembers its halts, its peak, its daily window and its open positions in a journal on disk. This is PersistentRisk in crates/zunder-exec/src/risk_store.rs, true today and used by Zunder’s testnet runner.

Each line is one JSON record. Lines are appended, never rewritten. Each line holds the engine’s complete state after the change, so loading needs only the last line (after checking all of them).

A line, shortened:

{"check":"9f2c…e41a","format":"zunder-risk-journal","version":2,"seq":17,
"at":1791000000000,"event":{"kind":"updated"},
"state":{"state":"active","peak":"2140.5","day":20729,"day_start":"2101.2",
"last":"2098.7","positions":[{"symbol":"BTC","side":"buy","qty":"0.03144",
"entry":"60000","stop":"58800","mark":"60210"}]},
"prev":"1b07…aa90"}
FieldMeaning
checkSHA3-256, in hex, of the exact bytes of the rest of the line
format, versionzunder-risk-journal, version 2. A newer version is refused, never guessed at
seq1, 2, 3, … without gaps
atwhen the change happened, UTC milliseconds
eventwhy the line was written (below)
statethe engine’s state after the change
prevthe previous line’s check; empty for the first

Because each line includes the previous line’s checksum, the lines form a chain. Changing, removing or reordering a line in the middle breaks it.

event.kindWritten byWhen
initialiseda personthe first line only: the limits, the network and the account
updatedthe enginethe state, peak, day, day start or positions changed
resumed_after_reviewa personafter a drawdown halt, with a note
repaireda persona torn last line was cut off, with a note
scopeda persononce, to name the network and account of an old journal

A falling equity alone is not written. The last equity matters only as the next day’s starting point, and a starting point that is too high makes that day’s loss look larger. So a missing write can only make the engine stricter.

The journal opens only if every line checks out. It refuses a journal that is:

  • missing, unreadable, or torn (the last line has no newline: a write was cut short);
  • damaged or reordered (a checksum or the chain does not match);
  • impossible (a step the engine cannot take: a lower peak, a drawdown halt cleared without a review, a daily halt cleared on the day it fired);
  • marked broken (a failed write leaves <journal>.broken next to it);
  • written under other limits, or for another network or account.

Each of these stops trading until a person has looked. Only a person starts a journal, cuts a torn line, removes a broken marker, or resumes. Changing the limits, even tightening them, starts a new journal with a new peak.

The writer runs the same checks before it appends, and never writes a line its reader would refuse. Each line is synced to disk before anything acts on it: a halt is on disk before a single position is closed. The journal is locked: a second process cannot open it.

  • An older copy. If someone replaces the journal with an earlier, complete copy, or cuts it back to an earlier complete line, the file itself looks fine. Nothing inside the file can tell.
  • Anything outside the engine. It records risk state, not every order. Zunder’s runner keeps a separate decision log for that.

This page as plain Markdown, for people and LLMs: /docs/concepts/journal.md