Skip to content

Production operations · Intermediate

Monitoring a live book

End-of-day checks that show their evidence, and that say so when they could not look.

14 min read5 referencesIntroduction to the series

Educational material only. Not investment advice.

Contents
  1. 01The daily loop
  2. 02What to measure each session
  3. 03Pass, fail and unavailable
  4. 04Witnesses and planted faults
  5. 05Explaining the day’s P&L
  6. 06Dashboards and checks
  7. —References

Abstract

Each evening we check that the broker holds what the strategy intended, that each instrument filled its intended change, that exposure is within limits, that prices are from today and that the day’s P&L is explained. Every check returns pass, fail or unavailable, and the report ranks unavailable above pass. Each result carries a witness, the dated evidence it read in this pass, and each check is trusted only after it has caught the fault it was written for.

Key takeaways

  • Compare every position the broker holds with what the strategy intended, and check exposure separately, because a wrong target passes the holdings check.
  • Every check returns pass, fail or unavailable, and the headline is fail, else unavailable, else pass.
  • Each result carries its witness, the dated evidence read in this pass; nothing is stored as passed and read back.
  • A check is trusted only after a planted fault has made it fail; a matrix of faults against checks shows where one check stands alone.
  • Explain the day’s P&L from holdings, price moves, fills and fees; the unexplained remainder points at its cause.
  • Dashboards help people explore; checks run whether or not anyone looks; only pre-trade controls prevent a bad order.

Before you start

  • Going live, for the live process being monitored
  • Data for systematic trading, for timestamps and calendars
  • TWAP, slicing and jitter, for orders and fills

Each evening a live book raises one question: did it do what the strategy intended? A monitor answers it with evidence or admits that it cannot. The most dangerous monitor is the one that cannot tell those two apart, because on the evening the broker stops answering it reports that all is well.

The daily loop

We run the same loop after every session. The strategy stamps its targets when it produces them. When the market closes, the monitor takes fresh snapshots from the broker (positions, fills, the statement of P&L) and from the price feed. Each check reads those snapshots, recomputes what it needs, and reports one of three results with the evidence it used. The report lists all the checks, each day, and only the results that need a person become alerts. The monitor also sends a heartbeat on each pass to a watcher that runs elsewhere, so a monitor that has stopped is noticed.

Figure 1The daily monitoring loop
Monitoring loop: strategy targets stamped when produced; broker snapshot and closing prices taken after the close; checks with three-valued results and witnesses; the daily report; alerts; a heartbeat to a separate watchertargetsheartbeatStrategy runtargets, stampedMarket closeBroker snapshotpositions, fills, P&LClosing priceswith their timestampsCheckspass, fail, unavailableDaily reportresults, witnessesAlertswhat needs actionWatcherelsewhere; expects a beat
Targets are stamped by the strategy; after the close the broker and the price feed are read afresh; the checks recompute from those snapshots and the report records every result with its witness. Only results that need action become alerts, and a separate watcher alerts if the monitor’s heartbeat stops.

What to measure each session

Each check compares two things that should agree and were produced independently. A position is only wrong relative to something, and the choice of that something decides what the check can catch.

CheckComparesCatchesCannot see
HoldingsEvery position at the broker with the strategy’s targets, including positions no strategy owns, and the targets’ timestamp with the sessionMissed, doubled and reversed orders; unknown positions; a strategy that did not runA target that was itself wrong
Orders and fillsOur order log with the broker’s fills: repeated order ids, and each instrument’s net fill with its intended changeDuplicates, partial and missed fills; slicing an order is fineA target that was itself wrong: the intended change is computed from it
SignsEach position’s side with its target’sA buy sent as a sellAnything short of a reversal
ExposureGross exposure of the broker’s positions, over the account’s equity, against its limitA strategy asking for too muchRisk inside the limit
FreshnessEach price’s timestamp, date included, against the closeYesterday’s prices, frozen feeds, time zones read wronglyA fresh price that is wrong
P&LThe broker’s P&L with holdings × price moves + fills − feesWrong prices, unexpected fees and chargesA doubled order: the broker’s own fills explain it

The six checks in the listings and the labs. A production suite adds more that this synthetic session does not model: continuity (yesterday’s positions plus today’s fills equal today’s), cash and margin, corporate actions, orders left working at the close, and the strategy’s own invariants.

Holdings are read from the broker, never from our own records, because our records are what a bug corrupts. The last column matters as much as the third. Holdings can equal targets even when the target itself is wrong: in the synthetic session, a strategy that asks for forty times its intended position passes the holdings check and is caught only by the exposure check.

Figure 2The reference decides what a check can see
A wrong target: the strategy asks for 40 times its intended E; the broker holds exactly the target, so the holdings check passes; the exposure check compares the holdings with a limit of 1.5 and fails at 2.24orders fillStrategyasks for 40× in ETargetE: 10,000Broker holdsE: 10,000Exposure limitgross 1.5 × equity, set apartHoldings check: passevery position equals its targetExposure check: failgross exposure 2.24 above the limit 1.5
Computed by monitor.py for the synthetic session, with one fault planted: the strategy asks for far too much of one instrument. The broker fills it faithfully, so the broker’s holdings equal the target and the holdings check passes; the exposure check compares the same holdings with a limit set outside the strategy, and fails.

Pass, fail and unavailable

A check has three possible results. It passed: it looked, and what it saw was right. It failed: it looked, and something was wrong. Or it was unavailable: it could not look, because the broker did not answer, returned an empty list, or the check itself crashed. The report’s headline is fail if anything failed, else unavailable if anything could not look, and pass only when every check looked and passed.

site_research/fieldnotes/monitor.py
def run(d: Day) -> list:
    """Every check, each isolated: a check that raises is UNAVAILABLE, never a crash of the report.
    On a day the exchange calendar has no session there is nothing to check, and each check says so."""
    if d.close_utc is None:
        return [Result(name, PASS, "no session today by the exchange calendar: nothing to check", "calendar: exchange closed")
                for name in CHECKS]
    out = []
    for name, fn in CHECKS.items():
        try:
            out.append(Result(name, *fn(d)))
        except Exception as e:                                   # noqa: BLE001
            out.append(Result(name, UNAVAILABLE, f"the check itself failed ({type(e).__name__})", "no result"))
    return out


def overall(results: list, unavailable_is_pass: bool = False) -> str:
    """The report's headline. Correctly: any FAIL, else any UNAVAILABLE, else PASS. The naive
    version, which many dashboards amount to, reads UNAVAILABLE as PASS."""
    s = {r.status for r in results}
    if FAIL in s:
        return FAIL
    if UNAVAILABLE in s and not unavailable_is_pass:
        return UNAVAILABLE
    return PASS
Each check is isolated: one that raises becomes unavailable, and the report is still written.

The rule matters on the evening the broker’s interface is down. On many dashboards an empty panel, a metric that stopped updating and a green light look the same at a glance.

Step by stepOne evening, a doubled order and a silent broker
Planted: an order sent twice, and the broker cannot be readtargets stamped19:40close20:00broker: no answer20:05Holdings◆ Unavailablebroker positions could not be readOrders and fills◆ Unavailablethe broker’s fills could not be readSigns◆ Unavailableno broker positions to compareExposure◆ Unavailableno broker positions to valueFreshness● Passevery price within 15 minutes of the closeP&L◆ Unavailablethe broker’s P&L could not be readThe report: fail, else unavailable, else pass◆ UnavailableA board that reads no data as fine● Pass, and wrong
  1. Step 1 of 4

    A clean evening

    The strategy stamps its targets at 19:40 UTC, the market closes at 20:00, and at 20:05 the monitor reads the broker. All six checks look, pass and say what they read: “6 broker positions read at 20:05, 6 targets from 19:40”. The report says pass.

  2. Step 2 of 4

    An order sent twice

    Now one order goes out twice. The holdings and orders checks fail and say where: “C: held 1,000, target 500”. The P&L check passes, because the broker’s P&L follows the broker’s own fills. One failure is enough, and the report says fail.

  3. Step 3 of 4

    The broker stops answering

    The same evening, the broker’s interface is down. Five of the six checks cannot look, and each says why; only the freshness check, which reads the price feed, still passes. No check can see the doubled order now.

  4. Step 4 of 4

    Unavailable is not a pass

    The three-valued report says unavailable: nobody knows what the book holds, and someone must find out before the next session. A board that reads missing data as fine says pass, on an evening when the broker holds 1,000 of C against a target of 500. Only the first reading sends someone to look.

Synthetic: the session of the laboratory below, through the six checks of monitor.py (the figure runs the TypeScript port, checked word for word against the Python). Without script, or with reduced motion, the figure shows its final state.

Figure 3Plant a fault, read the report
Plant a fault (any combination)
The report, three-valued
Unavailable
any fail, else any unavailable, else pass
A board that reads “no data” as fine
Pass
wrong: it could not look, and says all is well
Checks that could not look
5 of 6
each says why, in its witness
CheckResultWhat it foundWitness: what it read in this pass
Holdings equal targetsUnavailablebroker positions could not be readno position snapshot this pass
Every order filled onceUnavailablethe broker’s fills could not be read5 orders in our log, no fills
Positions on the right sideUnavailableno broker positions to compareno position snapshot this pass
Gross exposure within limitUnavailableno broker positions to valueno position snapshot this pass
Prices fresh at the closePassevery price within 15 minutes of the close6 prices, the oldest stamped 20:00
P&L explainedUnavailablethe broker’s P&L could not be readno statement this pass

Each check recomputes what it needs from this pass’s snapshots; nothing is read back from an earlier run.

Synthetic: one session of six instruments with targets, orders, fills, prices and the broker’s view. The lab opens with the broker unreadable. Add a price left at yesterday’s close; then clear both and plant only the oversized target, then only the unknown position.
ResultBefore the next session
FailFollow the check’s runbook; the book does not trade again until the failure is explained
UnavailableGet the evidence another way (the broker’s website, a statement, a second feed) and rerun the check; if it still cannot look, treat it as a failure
PassNothing, except reading the witnesses once a week to be sure they still say what they should

Witnesses and planted faults

Each result carries a witness: the evidence the check examined in this pass, with its date. “6 broker positions read at 20:05, 6 targets from 19:40” is a witness; “OK” is not. Witnesses make silent failures visible. A check that read nothing (an empty snapshot, a missing file) is unavailable, not passed, and says so. A price from the day before is named as such: “stale: B stamped the day before at 20:00”. We never store “the check passed” and read it back; each pass recomputes from snapshots taken in that pass.

A check is trusted only after it has been seen to fail. For each check we plant the fault it exists for and confirm that it fires, a practice borrowed from mutation testing in software, where a test suite is judged by the deliberate defects it catches.1 Run over all the planted faults, the synthetic suite gives the matrix below. Each planted fault is caught by at least one check, and 5 of the 11 rest on a single check: those are the places where a second, independent check would earn its keep.

Figure 4Which check catches which fault
Fault coverage: planted faults against checksHoldingsOrdersSignsExposureFreshnessP&LAn order is never filledfailsfailsAn order is sent twicefailsfailsA sell goes out as a buyfailsfailsfailsA price is yesterday’sfailsfailsPrices stamped in local timefailsThe broker cannot be readcannot lookcannot lookcannot lookcannot lookcannot lookThe broker returns no positionscannot lookcannot lookcannot lookA position the strategy does not knowfailsThe strategy asks for too muchfailsThe strategy did not runfailsAn unexpected feefails
Computed by monitor.py: each row is one planted fault, each column one check. Red cells fail, amber cells cannot look, green cells pass.
site_research/fieldnotes/monitor.py
def session_close_utc(day: date):
    """The session's close in minutes from midnight UTC of `day`, from the exchange calendar,
    or None when the exchange does not open that day."""
    if day.weekday() >= 5 or day in HOLIDAYS:
        return None
    utc = datetime.combine(day, EARLY_CLOSES.get(day, REGULAR_CLOSE), EXCHANGE_TZ).astimezone(ZoneInfo("UTC"))
    return (utc.date() - day).days * 1440 + utc.hour * 60 + utc.minute


def check_freshness(d: Day) -> tuple:
    if not d.price_utc:
        return UNAVAILABLE, "no prices were read", "empty price snapshot"
    age = {s: d.close_utc - t for s, t in d.price_utc.items()}
    stale = {s: a for s, a in age.items() if abs(a) > FRESH_MIN}
    w = f"{len(age)} prices, the oldest stamped {when(d.close_utc - max(age.values()))}"
    if not stale:
        return PASS, "every price within 15 minutes of the close", w
    offsets = set(stale.values())
    off = next(iter(offsets))
    if len(stale) == len(age) and len(offsets) == 1 and off % 30 == 0 and abs(off) <= 14 * 60:
        return FAIL, (f"every price is off by exactly {abs(off) / 60:g} h: check the time zone before "
                      "blaming the feed"), w
    return FAIL, "stale: " + ", ".join(f"{s} stamped {when(d.close_utc - a)}" for s, a in sorted(stale.items())), w

The close the check measures against is the session’s own, from the exchange calendar, never a fixed clock time. The regular close in New York falls at a different hour in UTC in summer and in winter, and earlier on a half-day, so a fixed time would report every price off by a whole hour for half the year and blame the time zone for what is really the calendar. On a holiday there is no session, and nothing to be fresh: the report says so for every check (run, in the first listing).

DateThe dayClose, UTC
2026-06-15the synthetic session (summer time)20:00
2026-01-15a winter session21:00
2026-11-27a half-day18:00
2026-11-26a holidayno session

From session_close_utc in monitor.py, which converts the exchange’s local close through the tz database. The calendar in the listing holds only the few dates this example needs; a production monitor reads the exchange’s published calendar.

Explaining the day’s P&L

The day’s P&L should be explainable from three things we already have: yesterday’s holdings times the day’s price moves, each fill marked to the close, and fees. Whatever the broker reports beyond that is unexplained, and its size and sign say where to look. A gap equal to a position times a price difference points at a price; a round number points at a fee; a gap that recurs every day points at something we do not model, such as financing or borrow.

Figure 5Explaining the day’s P&L
Plant a fault (any combination)

Positions, fills and our prices explain 2,058; the broker reports 2,458. The remaining 400 is unexplained, and the size and sign point to the cause: a price, a fee, or a fill we do not know about.

The day’s P&L, explained step by stepCarry A1,500Carry D−1,200Carry E750Carry F150Trading A120Trading C200Trading D640Trading F−90Fees−12Explained2,058Broker reports2,458Unexplained400
GainsLosses and feesExplained totalBroker’s own figureUnexplained
Synthetic: the same session. The bars build the day’s P&L from carry (yesterday’s holding times the move) and trading (fills marked to the close), less fees, and compare it with the broker’s figure. The lab opens with one price left at yesterday’s close. Add the unexpected fee; then clear both and double an order, and see what does not change.

When an order is doubled, this check still passes: the broker’s P&L follows the broker’s fills, and those fills explain it exactly. The P&L is right; the book is wrong, and the holdings and orders checks are the ones that say so. A P&L check run against the intended book instead would also catch it. The checks are designed as a set. Live versus backtest reconciliation compares the same holdings with the backtest’s, where a doubled order shows up at once.

Dashboards and checks

A dashboard answers the questions a person thinks to ask; a check answers one question every day whether anyone asks or not. Google’s site reliability engineers list dashboards and ad hoc analysis among the uses of monitoring, and keep alerts for symptoms that need a person now.2 Few’s rules for dashboard design, one screen with the exceptions made obvious, help the person looking;3 they cannot help on an evening when nobody looks.

Figure 6A check runs every session; a dashboard is seen when someone looks
A check runs every session; a dashboard is seen when someone looksthe fault is presentsession 12345678910Checkruns every sessionfails that eveningDashboardread when someone looksseen at the next looklooked: nothing wronglooked: the fault is visiblenobody looked
A schematic, not data: ten sessions with a fault from the fourth. The check fails on the evening the fault appears and on each evening after it. The dashboard shows the same fault, but only to someone looking at it, on the next evening someone does.
DashboardCheck
QuestionWhatever the viewer asksOne question, fixed in advance
RunsWhen someone looksEvery session, on a schedule
Missing dataAn empty panel, easily missedUnavailable, reported and alerted
Proof it worksNoneA planted fault it must catch
OutputPictures for a personPass, fail or unavailable, with a witness

The SEC’s order on Knight Capital describes the cost of relying on the first kind. Knight’s position monitoring tool relied entirely on people watching it, generated no automated alerts, and did not display the limits it was meant to police, and it could not stop the entry of orders, so the orders kept flowing while positions grew.4 End-of-day checks find problems after the fact; preventing them takes pre-trade controls in the order path, which the SEC’s market access rule requires of brokers and which Going live describes.5

Which of these results should wake someone is the subject of Alerting that people act on.

References

  1. DeMillo, R. A., Lipton, R. J., & Sayward, F. G. (1978). Hints on test data selection: Help for the practicing programmer. Computer, 11(4), 34–41. https://doi.org/10.1109/c-m.1978.218136
  2. Ewaschuk, R. (2016). Monitoring distributed systems. In B. Beyer, C. Jones, J. Petoff, & N. R. Murphy (Eds.), Site reliability engineering: How Google runs production systems (ch. 6). O’Reilly Media. sre.google/sre-book/monitoring-distributed-systems/
  3. Few, S. (2006). Information dashboard design: The effective visual communication of data. O’Reilly Media.
  4. U.S. Securities and Exchange Commission (2013). In the matter of Knight Capital Americas LLC (Exchange Act Release 34-70694). Administrative proceeding. www.sec.gov/files/litigation/admin/2013/34-70694.pdf
  5. U.S. Securities and Exchange Commission (2010). Risk management controls for brokers or dealers with market access (Exchange Act Release 34-63241). Final rule. www.sec.gov/files/rules/final/2010/34-63241.pdf