Your market emits a firehose of events; somewhere in it is one participant behaving badly — this week you build the pipeline that finds them, and you pay for every false alarm yourself.
A SIEM — security information and event management — is a streaming system that turns raw logs into a small number of things a person should look at. Structurally it is the same loop as week 3's trading system: events arrive, something decides, something acts. What changes is the actuator. The output of this pipeline is an analyst's attention, and that is the scarcest resource in the system.
Every SIEM, whether it costs nothing or millions, is these five stages. Each has its own cost, its own queue, and its own way of failing.
Read the source without losing it. Tail a file, consume a socket, replay a tape. Cheap per event, and the place where backpressure has to be decided.
Text → structure. Regexes and JSON loads. Usually the most expensive stage, and the one that silently drops what it cannot match.
Structure → one schema. A common event model, so a rule can be written once and run over an order tape and an auth log alike.
Rules and models over windows of normalised events. Where your accuracy-for-latency trade lives, exactly as in week 4.
Emit, deduplicate, group, route. Rare and expensive per unit, and the only stage whose consumer is a person.
The arena already produces the tape: every session is recorded to
sessions/session_*.jsonl and can be replayed deterministically with
scripts/replay_session.py. So the same pipeline you build for an sshd auth log
runs over your own market's order flow — which is the point of the normalise stage.
1 / cost, utilisation is ρ = λ / μ, and waiting time blows up
as ρ → 1 — not at ρ = 1. Micro-batching amortises
fixed per-pass overhead, so it raises throughput, and it adds a batch-fill wait, so it raises latency.
That is the whole streaming-systems trade in one slider.A detection rule is a predicate over a window of normalised events. Three of them catch most market-manipulation patterns, and all three are simple arithmetic — the difficulty is never writing the rule, it is choosing the threshold.
Post size you never intend to trade, to move the price, then cancel. The signature is a high cancel-to-fill ratio combined with size resting away from the touch. Honest market makers also cancel constantly — which is exactly why this rule generates false positives.
Trade with yourself to manufacture volume. The signature is a fill whose buyer and seller resolve to the same beneficial owner. Cheap to detect, and unambiguous — the rare rule that can run at near-perfect precision.
Flood the venue with messages to degrade everyone else's view. The signature is a
message rate far above peers with no corresponding fills. Note the arena already meters
this: exchange/limits.py answers RATE_LIMITED over quota.
| Participant | Msgs | Cancels/fill | Msg rate | Self-matches | Rules fired | Outcome |
|---|
Rules need a pattern you already know. For everything else you need a detector that learns “normal” online and flags departures — a rolling z-score, an EWMA residual, a half-space tree. All of them expose the same single dial, and turning it is the entire engineering decision: lower the threshold and you detect sooner and cry wolf more often.
A venue that cannot show a regulator how it surveils its own market has a business problem, not an engineering one. But the failure mode in practice is never “we had no detector” — it is “the detector fired, into a queue of nine thousand other alerts”. Precision is not a nicety. It is the constraint that decides whether your system functions at all.
Run the arithmetic once and it changes how you set thresholds forever. A million events a day, an alert on one in a thousand, and you have a thousand alerts. At ten minutes of triage each that is 167 analyst-hours a day against maybe twelve you actually have. The backlog is not a staffing gap; it is a design output, and you chose it when you picked the threshold.
All five ship. All five pass a demo. Each one turns a working detector into a dashboard nobody trusts.
You lower the threshold until the known-bad tape is caught 100% of the time, ship it, and drown. Recall is trivially purchasable — alert on everything and you have perfect recall — so a recall number with no precision beside it carries no information. The honest artefact is an operating point: this threshold, this precision, this recall, this many alerts per hour, and a sentence saying why that is the right corner of the curve for this venue.
The fix. Report precision, recall and alert volume together, always. Then choose the point by its cost: widget 4 converts a precision figure into analyst-hours, and analyst-hours is the currency the decision is actually made in.
Your window is “the last 60 seconds”, but which 60? Events carry a venue timestamp, arrive out of order, and your consumer stamps its own receipt time. Mix the two and a burst that happened inside one second gets spread across three windows, or worse, a replay of yesterday's tape lands in today's window and fires every rule at once.
The fix. Pick one clock per window, name it in the schema, and handle
late arrivals explicitly — a bounded lateness allowance and a watermark, or drop and count.
Deterministic replay (scripts/replay_session.py) is the test: the same tape must
produce the same alerts, every time.
A regex fails on a line format you did not anticipate, the exception is swallowed, and the event vanishes. Your detectors then run over a silently filtered stream, and the filter correlates with exactly the unusual behaviour you were trying to catch — the attacker's malformed input is the input your parser rejects.
The fix. Every stage counts what it dropped and why, and those counters are themselves monitored. A parse-failure rate that jumps is a security signal, not a housekeeping detail. Route unparsed lines to a dead-letter stream and look at it.
Detection costs 2 ms and events arrive every 0.5 ms, so the queue grows for ever. You drop nothing, which feels responsible, and your alert latency grows linearly with uptime: by hour three you are alerting on this morning. This is week 4's queue-up failure wearing a security hat, and it is worse here, because a late alert about an intrusion is not a degraded alert — it is a useless one.
The fix. Bound every queue and decide what happens when it fills: drop-oldest, sample, or shed to a cheaper detector. Then put alert latency on the dashboard next to alert count, and make an SLO out of it — p99 end-to-end, not the mean.
One cancelled order is not spoofing. One failed SSH login is not a brute-force attempt. Rules written per event produce a stream of individually meaningless alerts, and the analyst becomes the aggregation layer — badly, at three in the morning. The manipulation lives in the ratio and the rate, which are properties of a window, not of a record.
The fix. Make the window a first-class part of the rule, and alert on the entity, not the event: one alert per participant per window, with the evidence attached and a count. Then deduplicate and group before routing — an incident, not a thousand notifications.
Instant feedback, nothing recorded. Pick the best answer.
Stream your own arena session tape through a parser, three rule detectors and one online anomaly model. Then defend an operating point and write the incident report on a seeded manipulation tape — what fired, what it missed, and what you would have told the venue to do.
make install. Labs and deliverables live in your course's own system.