Audit trail: what the engine records, and what it does not
A compliance officer, a risk officer or a regulator opens this record with one question: for a given order that left this installation, what decided to send it, against what state, and when. The engine answers that question from one file on the machine that ran it. Every row of the retained trades table in that file is one execution, stated completely and never afterwards altered. What follows is what the record establishes, what it does not, and what has to be kept beside it.
What this record is
One file, one row per execution
An account created under a storage regime that includes the database writes a row for every trade it retains: the rule and the robot that produced it, the market event it responded to, the moment it executed, what executed, and the state of both the position and the whole portfolio as they stood immediately before it was applied. The record is read the way a reviewer reads it, not the way a researcher reads it — the question it exists to answer is why a particular order was sent, and the columns are grouped to close that question.
What is written for every trade
The fields, in the order a reviewer asks for them
Which order this is and whether it matches the broker's own record: the client order identifier and the broker order identifier. Which instrument was traded: the contract, resolving to the symbol. Which decision produced it: the rule label, stored inline, and the robot, resolving to the robot label. What the decision was responding to: the timestamp of the market event that fired the pattern. When it executed: the timestamp the execution point assigned to the fill. What executed: price, quantity, fee and booked profit and loss. What kind of order it was: the price type, time in force, quantity type and priority codes, with the transaction side, position side, book mode and execution codes classifying the transaction. What the position looked like the instant before: the contract exposure snapshot and the position extremes. What the whole portfolio looked like the instant before: the portfolio exposure snapshot.
The two snapshots are observations, not reconstructions
The engine reads the contract exposure and the portfolio exposure, applies the fill to them, and only then hands the trade and both snapshots to the blotter. The insert takes exactly three things — the trade and the two snapshots — and writes them in one transaction. The state a row reports is the state that existed before that row's fill, captured at that moment rather than recomputed afterwards from the sequence of trades. A reviewer who recomputes the position from the trades and gets a different answer has found a real discrepancy, not a rounding of the journal.
A row is a fill, not an order
The quantity, price and fee written into a row are those of one fill report. An order filled in slices produces one row per slice, each with its own snapshot pair, and the order is reassembled by grouping on the client order identifier.
A reversal is journaled as two rows
When a transaction crosses through flat — closing a position and opening the opposite side in one execution — the blotter splits it into the part that closes the existing round trip and the part that starts the new one, writes both, and marks both with the split book mode. The two rows share their order identifiers and their timestamps. The second carries a neutral contract-exposure snapshot rather than an observed one, because by construction it begins from flat; its portfolio snapshot is the same observed one as the first row's. Booked profit and loss is booked on the closing part only: it is realized when exposure moves toward zero, and a row that increases exposure carries only the negated fee.
Where it lives and who owns it
On your filesystem, and nowhere else
The blotter file and the scoring file stay on the machine that wrote them. Everything the engine sends anywhere else goes through a connector you configured or a processor you wrote, to an address you chose. There is no account to open, no upload step, and no copy held by anyone but you.
What is guaranteed
Rows are written once and never changed
The insert resolves a conflict by discarding the incoming row, never by updating the stored one. There is no update, no delete and no drop against this table anywhere in the engine or in the exported surface: the only way a stored row changes is if something outside the library edits the file. Keys are never reused, so a gap in the sequence is evidence that a row was removed by something other than the engine.
Write order is recoverable; time order is not asserted
The row identifier increases with the order in which rows were journaled, and it is the only column that carries that guarantee. Neither timestamp is checked for monotonicity anywhere in the engine. The execution timestamp is assigned by the execution point — the simulator in a backtest, the venue in production — and is written as received; the market-event timestamp is likewise written as received. Order the journal by identifier when the question is what the engine did in sequence, and by a timestamp only when the question is about wall-clock time.
The two timestamps are independent and are meant to be compared
The market-event timestamp is a property of the data the decision was taken on; the execution timestamp is a property of the venue's report. Their difference is the latency of the whole path from market event to fill, and neither is derived from the other.
The write is transactional and blocking
The insert opens an explicit transaction, writes the robot's row and the trade's row, and commits only if the statement completed; any failure rolls the whole transaction back. A lock contended by another writer is retried five times at hundred-millisecond intervals before the write is reported failed. When the blotter runs on a dedicated storage thread, the caller submits the work and waits for it, so there is no queue of pending writes that a crash could discard.
Durability is bounded, and the bound is stated here
The database runs in write-ahead logging mode with synchronous commits off, which means no flush to disk is forced at commit. The consequence splits in two, and the halves are not interchangeable. If the process dies — a signal, an uncaught exception, an abort — committed rows survive, because the data has already been handed to the operating system and its flushing does not depend on the process. If the operating system dies or power is lost, there is no guarantee: committed transactions may be lost and the write-ahead log may be left damaged. An orderly shutdown checkpoints the database into the main file; an abrupt one leaves that to the ordinary log replay at the next open. An installation that must survive power loss has to raise that setting, and it is an internal default today rather than something the public surface exposes.
What the scoring store is not
Derived analytics, overwritten in place
The scoring database is a separate file with its own tables, and it is not part of the audit trail. It holds per-robot summaries bucketed by duration and the scores computed from them. Every write into it is an upsert against that table's declared key, so a row whose key already exists is overwritten in place and what it held before is gone. Re-running a scoring pass over the same robot and the same bucket replaces the earlier numbers without leaving a trace that they were ever different.
Which is correct for what it is — and is said here first
That is the right behaviour for a derived store, and a derived store is what it is: everything in it is recomputable from the trades. It is stated on this page so the distinction is on the record before anyone infers otherwise. The retained trades are the evidence; the scoring store is a view over the evidence, and only the retained trades are insert-only.
What you must keep yourself
The engine persists no market data at all
The chain a reviewer wants to walk runs from data through firing and rule to risk check and order. The journal covers that chain from the rule onward. The first links are not in it: there is no table, anywhere in the engine, holding quotes, bars, book messages, input values or pattern values. The market-event timestamp is a pointer to the instant, not a copy of what was read at that instant.
Three things close the chain, and they are yours to retain
First, the input stream, timestamped on the same scale — both timestamp columns are nanoseconds since the Unix epoch, so a retained recording of the ticks fed to the adapters, carrying those same timestamps, joins to the journal on equality. Without it, what the strategy saw when it fired cannot be reproduced. Second, the recipe the robot ran under: inputs, patterns and rules with their parameters, as saved by the save-and-load machinery. Third, the code behind every registered processor key — a recipe stores the key of a processor, not its body, and two runs under the same key and different code are indistinguishable in the journal and in the recipe alike.
One partial exception
Every pattern publishes an order when it fires, and a rule reads from it the fields its own parameters left undefined. Where a rule declares its quantity undefined, the quantity the signal carried is the quantity of the order, so that number is recoverable from the quantity column — but it is the number the formula put there, not the value that fired the pattern, and the five analytic kinds publish a zeroed order and so offer none. Where a rule fixes its quantity at build time, the column holds that fixed size. In no configuration is the input value that fired the pattern persisted in any form.
What the record does not carry today
A refused order leaves no row
The engine refuses an order before it is sent in two places: when reserving the order's exposure fails, and when a risk policy does not hold at submit time. Both paths set a diagnostic message, write a log line, roll back every reservation the submission took, and return. Neither touches the database. The refusal is real and it is recorded, but it is recorded in the rotating text log — appended, rotated daily and capped by size — and not in any table. Reconstructing the moment and the reason of a refusal after the fact is possible only from that log file, and only while it is still within the rotation window. There is no table of refusals to query.
Nothing in a row identifies a version
The rule label and the robot label are labels. There is no hash of the recipe, no library version, no build identifier and no configuration fingerprint in the table. Two runs sharing a rule label and a robot label but differing in logic produce rows that cannot be told apart; conversely, renaming a rule whose logic never changed presents as two different strategies. The engine does keep a robot descriptor store, but it does not close this gap: it is a separate file with no key into the journal, it is keyed on the same mutable label, and it is written so that it holds the current descriptor and no history of previous ones. Establishing that the code and configuration behind two rows were the same requires evidence you keep outside the library.
Uniqueness is enforced by discarding, and it is silent
The compound constraint spans ten columns and deliberately excludes both order identifiers and the price, which is what makes journaling idempotent against a replayed or redelivered execution report. The same mechanism means two genuinely distinct fills that agree on all ten columns yield one row rather than two, and the discard produces no diagnostic. Completeness of the journal is therefore exact up to that key, not absolute. The practical bound is narrow — the key includes both timestamps at nanosecond resolution, so the collision requires two fills of equal size on the same contract, rule, robot and booking mode at the identical nanosecond — but it is a bound, and it is stated here rather than left to be discovered.
What leaves the machine
Nothing of this record travels, and nothing of it can
The engine reaches the network on its own behalf for one purpose, the licence check. No market data, no contract, no input value, no pattern, no rule, no strategy, no order, no trade and no statistic has a field to travel in. The claim is structural rather than a matter of policy: the message has no member of those types. The reference chapter enumerates field by field what that exchange does carry.
Browse the full reference Why not LEAN, and why not build it yourself Questions and answers