Quantitative Finance · Book 15 · Technology

Research, Data and Risk Platforms

Research, Data and Risk Platforms · Technology

18Real-Time Risk

Each of two desks was inside its limit all afternoon. Together they were short almost twice the firm’s appetite in the same index, and nobody saw it until the end-of-day batch, because nothing added the desks up while they traded. Each desk’s system answered the question it was asked — am I inside my limit? — and the question nobody’s system was asked is the one that mattered. This chapter builds the aggregator that asks it on every fill and every market move: exposures added up the risk hierarchy incrementally, sensitivities cached and refreshed when the market moves, limits checked in the loop, and a what-if answer before a trade is sent.

18.1 What real-time means for risk

Definition 18.1 (Real-time risk system)

A real-time risk system computes the firm’s exposures and limit usage continuously from the position service’s executions and the market’s moves, fast enough that a breach is seen while the trading that causes it can still be stopped, and serves them with their timestamps.

The end-of-day risk engine of Book 6 (chapter 29) revalues every position under hundreds of scenarios once a night; the pre-trade gate of Book 13 (chapter 22) checks one order in microseconds against limits it holds in memory. Real-time risk sits between them: seconds, not nanoseconds or hours, and the whole firm, not one order. Its numbers are coarser than the nightly engine’s (sensitivities, not full revaluation) and its scope wider than the gate’s (every desk at once). The timeliness of risk data is one of the principles of BCBS 239 (Book 6, chapter 29): a figure available the next morning describes a risk that has already been taken.

18.2 Aggregation along the hierarchy

Definition 18.2 (Exposure aggregation)

Exposure aggregation adds trade-level exposures — here delta-dollars in the index — up the risk hierarchy (trade, book, desk, firm) so that every node carries the sum of its descendants and can be compared with its own limit.

A desk’s limit and the firm’s are not a partition: the firm’s appetite is usually less than the sum of its desks’ limits, because desks are not expected to use their limits at the same time and in the same direction. That is exactly what they sometimes do. The chapter’s hierarchy has one firm, two desks and a book each; the firm’s appetite and each desk’s limit are $50 million of index delta, which firm.riskctl’s rule allows (a desk’s limit never above the firm’s).

The chapter’s risk hierarchy at the close, with each node’s limit and delta. Every node is inside its limit except the one no desk’s system computes.
Figure 18.1. The chapter’s risk hierarchy at the close, with each node’s limit and delta. Every node is inside its limit except the one no desk’s system computes.

18.3 Incremental updates

Definition 18.3 (Incremental aggregation)

Incremental aggregation updates aggregates by adding only the change each event causes — a fill’s exposure, a trade’s change after a market move — to the node and its ancestors, instead of recomputing the tree from all trades.

Definition 18.4 (Sensitivity cache)

A sensitivity cache keeps each trade’s sensitivities (delta, gamma, vega) computed by the pricing library at one market snapshot, uses them to carry exposures forward as the market moves, and recomputes them when the market has moved more than a threshold since.

The chapter’s aggregator (firm.rtrisk) keeps, for each trade, the delta-dollars it currently contributes. A fill adds the new contribution minus the old to the trade’s book and every ancestor. A market tick carries each cached delta forward with its gamma, (Δ+Γ dS) q S(\Delta + \Gamma\,\mathrm{d}S)\,q\,S, and applies the differences; when the spot has moved more than 0.5% since the cache was filled, every instrument is repriced by Book 5’s library (firm.pricing, bump and reprice) and the differences are applied again. The aggregates are never rebuilt; the tests check that they always equal a rebuild.

class SensitivityCache:
    """Greeks per instrument, computed at one snapshot, valid while the spot stays near it."""

    def __init__(self, md: FP.MarketData, underlying: str, threshold: float = 0.005):
        self.md, self.und, self.threshold = md, underlying, threshold
        self.asof_spot = md.spots[underlying]
        self.cache: dict = {}
        self.refreshes = 0

    def greeks(self, inst) -> dict:
        if inst.id not in self.cache:
            if isinstance(inst, Future):
                self.cache[inst.id] = {"delta": 1.0, "gamma": 0.0, "vega": 0.0}
            else:
                g = FP.greeks(inst, self.md, which=("delta", "gamma", "vega"))
                s = self.asof_spot
                gamma = g["gamma"] * 100.0 / (s * s)          # per unit of spot, squared
                self.cache[inst.id] = {"delta": g["delta"], "gamma": gamma, "vega": g["vega"]}
        return self.cache[inst.id]

    def stale(self, spot: float) -> bool:
        return abs(spot / self.asof_spot - 1.0) > self.threshold

    def refresh(self, md: FP.MarketData) -> None:
        self.md, self.asof_spot, self.cache = md, md.spots[self.und], {}
        self.refreshes += 1
Listing 18.1. The sensitivity cache: Greeks from the pricing library at one spot, stale beyond a threshold. code/firm/rtrisk/firm_rtrisk.py
    def _dd(self, inst, qty: float) -> float:
        """Delta-dollars of a trade now, from the cache: (delta + gamma dS) qty S."""
        g = self.cache.greeks(inst)
        ds = self.spot - self.cache.asof_spot
        return (g["delta"] + g["gamma"] * ds) * qty * self.spot

    def _add(self, book, amount: float) -> None:
        for node in self.h.ancestors(book):
            self.expo[node] = self.expo.get(node, 0.0) + amount
        self.updates += 1

    def _check(self, t: float) -> None:
        for node, lim in self.limits.items():
            e = self.expo.get(node, 0.0)
            if abs(e) > lim and not any(a[1] == node for a in self.alerts):
                self.alerts.append((t, node, e, lim))

    def on_fill(self, t: float, book, key, inst, qty: float) -> None:
        old = self.trades.get(key)
        qty_total = qty + (old[2] if old else 0.0)
        self.trades[key] = (book, inst, qty_total)
        new = self._dd(inst, qty_total)
        self._add(book, new - self.contrib.get(key, 0.0))
        self.contrib[key] = new
        self._check(t)

    def on_tick(self, t: float, md: FP.MarketData) -> None:
        self.spot = md.spots[self.cache.und]
        if self.cache.stale(self.spot):
            self.cache.refresh(md)
            self.cache_t = t
        for key, (book, inst, qty) in self.trades.items():
            new = self._dd(inst, qty)
            self._add(book, new - self.contrib[key])
            self.contrib[key] = new
        self._check(t)
Listing 18.2. Incremental aggregation: a trade’s contribution, its change added to every ancestor, and limits checked, on every fill and every tick. code/firm/rtrisk/firm_rtrisk.py

Definition 18.5 (Risk snapshot)

A risk snapshot is the aggregator’s state at one time: every node’s exposure, the market time it reflects, the time and spot of the sensitivities it used and their age, and the limits breached.

A snapshot that does not say how old its sensitivities are cannot be trusted; one that does can be used with judgement. At the close of the chapter’s afternoon the cache was last refreshed 65 minutes earlier (3 890 seconds): the spot had not moved 0.5% since, so the carried deltas were within the threshold’s accuracy.

18.4 Sensitivities, full revaluation and the gap

Carrying exposures with cached sensitivities is fast and approximate; full revaluation is exact and slow. The gap is measured by pricing desk A’s options at the close both ways for moves from −10%-10\% to +10%+10\% (Figure 18.2): the delta-gamma P&L from sensitivities computed at the close against the change of the options’ full values.

The error of the P&L computed from cached delta and gamma, as a share of the full revaluation’s, for desk A’s 21 000 index puts at the close, by the size of the index move. It is zero at ±1\% (where the Greeks’ bumps were), below 1% between -6.75\% and +3.5\%, and grows fast on the upside, where the puts’ convexity changes most. Data: fig_rtrisk.py.
Figure 18.2. The error of the P&L computed from cached delta and gamma, as a share of the full revaluation’s, for desk A’s 21 000 index puts at the close, by the size of the index move. It is zero at ±1%\pm1\% (where the Greeks’ bumps were), below 1% between −6.75%-6.75\% and +3.5%+3.5\%, and grows fast on the upside, where the puts’ convexity changes most. Data: fig_rtrisk.py.

Inside the range a real-time system meets in a day, the approximation is excellent: the error is exactly zero at ±1%\pm 1\%, because the library computed delta and gamma from bumps of ±1%\pm 1\% and the quadratic passes through them, and stays under 1% of the full revaluation from −6.75%-6.75\% to +3.5%+3.5\%. Beyond, it fails fast — 3.1% at +5%+5\%, 23.6% at +10%+10\% — which is why the cache is refreshed on moves far smaller than those, and why stress numbers come from full revaluation (Book 6, chapter 29), not from sensitivities.

18.5 Limits in the loop

Definition 18.6 (What-if check)

A what-if check computes what a proposed trade would do to the exposure of every node above its book, and which limits it would breach, before the trade is sent, without changing the aggregator’s state.

    def what_if(self, book, inst, qty: float) -> tuple[dict, list]:
        d = self._dd(inst, qty)
        after = {n: self.expo.get(n, 0.0) + d for n in self.h.ancestors(book)}
        breached = [n for n, e in after.items() if n in self.limits and abs(e) > self.limits[n]]
        return after, breached
Listing 18.3. The what-if check: the trade’s contribution added to its ancestors on a copy, and the limits it would breach. code/firm/rtrisk/firm_rtrisk.py

A what-if check is the real-time system’s contribution to pre-trade control: the gate of Book 13 checks an order against limits in microseconds, the what-if tells it whether the firm-level limit has room. At the close of the chapter’s afternoon, desk B asking to sell 400 more futures would be told that its desk stays at −$45.2-\$45.2 million, inside its limit, and the firm goes to −$92.8-\$92.8 million, far outside.

18.6 Two desks, one index

The chapter’s afternoon runs from 13:00 to 17:30 on an index starting at 5 000, with seeded moves (20% annual volatility and a drift of −1.5%-1.5\% over the afternoon; it closes at 4 908.5). Desk A buys index puts — six listed lines, three strikes and two expiries, 1 000 units at a time — and desk B sells index futures, 400 at a time, each at random moments, about every five minutes. Before each trade the desk checks its own exposure and trades only if it stays under 90% of its limit. Executions go through the position service of chapter 17; the firm’s aggregator adds everything up.

Index delta of the firm and of each desk through the afternoon, in the aggregator. Neither desk goes beyond its limit — desk A’s largest exposure is -\$49.3 million, desk B’s -\$43.6 million — while the firm crosses its own at 14:23:50 and ends at -\$90.8 million. Data: fig_rtrisk.py.
Figure 18.3. Index delta of the firm and of each desk through the afternoon, in the aggregator. Neither desk goes beyond its limit — desk A’s largest exposure is −$49.3-\$49.3 million, desk B’s −$43.6-\$43.6 million — while the firm crosses its own at 14:23:50 and ends at −$90.8-\$90.8 million. Data: fig_rtrisk.py.

Example 18.7 (Two desks, one index)

Both desks did what their systems allowed: desk A made 21 trades and holds 21 000 puts, desk B made 22 and is short 8 800 futures; neither exceeded its limit at any moment. The firm’s limit was breached at 14:23:50, the aggregator’s only alert of the afternoon; at the close the firm is −$90.8-\$90.8 million of delta, 1.82 times its appetite (desk A −$47.6-\$47.6 million, desk B −$43.2-\$43.2 million). Without the aggregator, the end-of-day batch would have found it three hours after the breach. The cache was refreshed six times; the aggregator made 10 570 updates.

Remark 18.8 (Who acts on the alert)

A firm-level breach belongs to no desk. The aggregator’s alert has to reach someone with authority over both — the risk manager who can block new risk-increasing orders through the gate, or reduce a desk’s limit on the spot (chapter 16’s parameter store, with its approvals) — and the limit framework has to say in advance whose risk is cut first. A real-time number that nobody may act on is only a faster report.

18.7 Tutorial: two desks and an aggregator

Goal. Aggregate two desks’ exposures incrementally, find the firm-level breach the desks could not see, and measure the sensitivity cache’s approximation. End state: Figures 18.3 and 18.2.

  1. The afternoon: pl_rtrisk.afternoon(): the index path, the desks’ trades, the aggregator’s alerts.
  2. Snapshots: agg.snapshot(t) at any time: exposures, cache age, breaches.
  3. What-if: what_if_at_close(r).
  4. The gap: approximation_error(r) and one_percent_move.

What to change next. Lower the refresh threshold to 0.1% and count the repricings; give desk A calls instead of puts and see whether the firm still breaches.

18.8 Build: the real-time aggregator

Purpose. The firm’s exposures and limit usage, every node of the hierarchy, updated on every fill and tick, with what-if answers for proposed trades.

Interface. Hierarchy; SensitivityCache(md, underlying, threshold); Aggregator(hierarchy, cache, limits) with on_fill, on_tick, exposure, snapshot, what_if, alerts; Future; delta_gamma_pnl, full_pnl.

Rules. Aggregates are updated by differences, never rebuilt; sensitivities come from the pricing library and are refreshed beyond a threshold; every snapshot carries the age of its sensitivities; limits are checked on every update; a what-if never changes state.

Acceptance tests. code/firm/rtrisk/tests/: the hierarchy and the incremental sums against a rebuild; ticks inside the threshold (carried with gamma) and beyond it (repriced); limits, alerts and what-if; the delta-gamma P&L against full revaluation for a small move.

Stretch. Vega and rates exposures on the same hierarchy; many underlyings with a correlation-aware firm number; the aggregator fed from the event log of chapter 24.

Sources and further reading

  • One Quant Book 5, chapters 4 and 28 (Greeks, the pricing library); One Quant Book 6, chapters 21 and 29 (sensitivities, the risk engine and its hierarchy, BCBS 239); One Quant Book 11, chapter 27, and Book 13, chapter 22 (limits and the pre-trade gate).

18.9 Exercises

Exercise 18.1 ★

A put with delta −0.4-0.4 on 1 000 units of an index at 5 000: what is its delta in dollars, and how many such trades fit under 90% of a $50 million limit?

Solution

Solution of Exercise 18.1.

−0.4×1 000×5 000=−$2-0.4 \times 1\,000 \times 5\,000 = -\$2 million of delta. Under 90% of $50 million ($45 million), 22 such trades fit, before gamma and the market move the deltas.

Exercise 18.2 ★

Why is the approximation error exactly zero at ±1%\pm 1\% in Figure 18.2?

Solution

Solution of Exercise 18.2.

The library computes delta and gamma by central differences with bumps of ±1%\pm1\% of the spot, so the quadratic Δ dS+12Γ dS2\Delta\,\mathrm{d}S + \frac12\Gamma\,\mathrm{d}S^2 passes exactly through the two repriced points: at ±1%\pm1\% it is the full revaluation.

Exercise 18.3 ★

What does the what-if check tell desk B at the close, and why can desk B’s own system not tell it that?

Solution

Solution of Exercise 18.3.

That its desk would stay at −$45.2-\$45.2 million, inside its limit, and the firm would go to −$92.8-\$92.8 million, breaching the firm’s. Desk B’s system knows only desk B’s trades; the firm’s number needs desk A’s too.

Exercise 18.4 ★★

Why does the error grow faster on the upside than on the downside for a book of long puts?

Solution

Solution of Exercise 18.4.

The quadratic keeps gamma constant. A long put’s gamma falls as the index rises and the put goes out of the money, and its value flattens towards zero, so the quadratic, still curving, misstates the loss of value more and more on the upside; on the downside the puts go into the money and behave more linearly, closer to the quadratic.

Exercise 18.5 ★★

The snapshot at the close used sensitivities 65 minutes old. When is that acceptable, and what would make it not?

Solution

Solution of Exercise 18.5.

Acceptable while the spot stays within the refresh threshold of the cache’s spot (then the carried delta is accurate to the threshold’s error) and nothing else the sensitivities depend on has moved; not acceptable after a volatility shock, a large move in a correlated factor, a change of the book the cache has not seen, or time passing near expiry.

Exercise 18.6 ★★

Why is incremental aggregation checked against a rebuild in the tests, and what defect would the check catch?

Solution

Solution of Exercise 18.6.

Because the aggregator never recomputes the tree, an error in one difference — a contribution not updated, an old value subtracted twice, an ancestor missed — persists and accumulates silently. Comparing with a rebuild after fills, ticks and refreshes catches any such drift.

Exercise 18.7 ★★★

Coding. Run afternoon with a refresh threshold of 0.1% and of 2%. How many repricings does each make, and does the firm’s exposure at the close change?

Solution

Solution of Exercise 18.7.

At 0.1% the cache is repriced 105 times, at 2% once; the firm’s exposure at the close is −$90.837-\$90.837 million and −$90.832-\$90.832 million, and the breach time is the same, 14:23:50. For this book and afternoon the threshold changes the cost, not the answer; a larger move or a shorter-dated book would change that.

Exercise 18.8 ★★★

Find the flaw. “Every desk has a limit and a pre-trade check, so the firm cannot exceed the sum of the desks’ limits, and we have set that sum below the firm’s appetite.”

Solution

Solution of Exercise 18.8.

Desk limits bound each desk at the moment of each trade; exposures drift with the market after trades (gamma), limits are often set so that their sum exceeds the firm’s appetite, and even when the sum is below it, desks’ exposures in correlated underlyings can compound while netting in the limit arithmetic does not. Only an aggregate computed across desks, continuously, shows the firm’s number.

18.10 Problem: Two Desks, One Index

Problem 18.1

Weekend problem — a breach nobody’s system could see

The chapter’s afternoon, desks and aggregator.

Part I — The system.

  1. What does real-time mean here, compared with the nightly engine and the pre-trade gate?
  2. How are exposures aggregated, and why are desk limits not a partition of the firm’s?
  3. What does an incremental update add, on a fill and on a tick?
  4. When is the cache refreshed, and by what?
  5. What does a snapshot carry?

Part II — The afternoon.

  1. What did each desk trade, and what did it check?
  2. What were the desks’ largest exposures?
  3. When was the firm’s limit breached?
  4. What was the firm’s exposure at the close, as a multiple of its appetite?
  5. What does the what-if check say at the close?

Part III — The approximation.

  1. How is the cache’s P&L compared with full revaluation?
  2. Between which moves is the error under 1%?
  3. What is it at +5%+5\% and at +10%+10\%?
  4. Why is it zero at ±1%\pm1\%?
  5. What follows for stress numbers?

Part IV — The verdict.

  1. State the named result: the time at which the firm-level limit was breached while each desk stayed inside its own, the exposure at the close, and the size of the market move beyond which the cache’s P&L error exceeds one percent of full revaluation.
  2. Who should receive the alert, and what should they be able to do?
  3. How would you set the refresh threshold?
  4. What else would you aggregate in real time?
  5. In one sentence: why must risk be added up while it is taken?
Solution

Solution of Problem 18.1.

  1. Seconds and the whole firm, between the nightly engine (hours, full revaluation) and the gate (microseconds, one order).
  2. Trade delta-dollars summed up the hierarchy; desks are not expected to use their limits together, so the firm’s appetite is less than their sum.
  3. A fill adds its new contribution minus its old to every ancestor; a tick adds, per trade, the change of its carried delta-dollars.
  4. When the spot has moved more than 0.5% since the cache was filled; by repricing every instrument with the pricing library.
  5. Exposures, the market time, the cache’s spot and age, and the breached limits.
  6. Desk A bought puts, 1 000 at a time; desk B sold futures, 400 at a time; each checked that its own exposure stayed under 90% of its limit.
  7. −$49.3-\$49.3 million and −$43.6-\$43.6 million.
  8. At 14:23:50.
  9. −$90.8-\$90.8 million, 1.82 times.
  10. Desk B inside at −$45.2-\$45.2 million, the firm at −$92.8-\$92.8 million, breaching.
  11. Delta-gamma P&L from Greeks computed at the close against the change of full values, for moves of −10%-10\% to +10%+10\%.
  12. −6.75%-6.75\% and +3.5%+3.5\%.
  13. 3.1% and 23.6%.
  14. The Greeks were computed from bumps of ±1%\pm1\%.
  15. They come from full revaluation.
  16. Named result. The firm’s limit was breached at 14:23:50 while neither desk exceeded its own (largest −$49.3-\$49.3 million and −$43.6-\$43.6 million against $50 million); at the close the firm was −$90.8-\$90.8 million, 1.82 times its appetite; the cache’s P&L error exceeds 1% of full revaluation beyond a move of +3.5%+3.5\% (or −6.75%-6.75\%).
  17. A risk manager with authority over both desks, able to block risk-increasing orders at the gate and cut a limit through the parameter store.
  18. From the approximation error: small enough that the error stays well under the precision limits are set with (here, under 1% up to 3.5%), and large enough that repricing costs are bounded.
  19. Vega, rates and credit sensitivities, gross and net notional, P&L against loss limits, and concentrations by issuer.
  20. Because a limit breached across desks is created while they trade and is only useful to know while it can still be stopped.

18.11 Interview questions

Interview question 18.1 ★ developer, researcher

Two desks are each inside their limits. How can the firm be outside its own?

Solution

Solution of Interview question 18.1.

Desk limits are set on each desk alone and usually add up to more than the firm’s appetite; exposures in the same direction and underlying add, and gamma moves exposures after the trades.

What the interviewer is looking for: Aggregation across desks.

Interview question 18.2 ★★ developer

How would you keep firm-wide exposures current as thousands of fills and ticks arrive per second?

Solution

Solution of Interview question 18.2.

Incremental aggregation up a hierarchy: each event adds its difference to its ancestors; sensitivities cached and refreshed on thresholds; limits checked on the updated nodes only; snapshots with timestamps; a periodic rebuild as a check.

What the interviewer is looking for: Differences, not recomputation; caches with invalidation.

Interview question 18.3 ★★ researcher

When is a delta-gamma approximation of P&L good enough, and when must you revalue?

Solution

Solution of Interview question 18.3.

For moves within the range where its error is small against the decisions made with it — here under 1% up to 3.5% — and for books without strong higher-order terms; revalue for large moves, stress, barriers and short-dated options near the strike.

What the interviewer is looking for: Measure the error against the move size.

Interview question 18.4 ★★ developer

What should a risk snapshot tell its reader besides the numbers?

Solution

Solution of Interview question 18.4.

The market time it reflects, the time and market point of the sensitivities used and their age, which positions are pending, and which limits are breached.

What the interviewer is looking for: Timestamps and staleness.

Interview question 18.5 ★★★ developer

Design a real-time risk system for a multi-desk trading firm.

Solution

Solution of Interview question 18.5.

Executions from the position service and market data from the feed into an aggregator over the risk hierarchy; sensitivities from the pricing library in a cache with invalidation; incremental updates; limits from a parameter store checked on every update with alerts to people who can act; what-if checks for the pre-trade gate; snapshots with staleness; full revaluation periodically and for stress.

What the interviewer is looking for: Hierarchy, incrementality, cache, limits in the loop.

Terms defined in this chapter

See all 2333 terms in the glossary