← All examples
06

Canonical Grid model

Portfolio Risk Engine

Large portable example for portfolio translation, weighting, carry, and duration stress.

Scale
Large
Source
06-portfolio-risk-engine.grid
Length
129 lines
Collection
Forecasting & decisions
Level
Intermediate
Runtime
External FX function
Version
1.0.0

Watch it in Grid

See this model in motion.

Watch the model respond in the product, then inspect the exact source and checkpoints on this page.

Companion film

Portfolio risk engine

Trim one position and see totals, carry, duration, stress, and concentration move as one connected portfolio.

21 secGrid 0.61.0
Open film page

What this model gives you

An eight-position portfolio translated into USD with inspectable FX work, carry and duration summaries, and symmetric 200-basis-point stress results.

30 min to study · Source reviewed 2026-08-26

Continue with guided practice

What to notice

  • Provider-backed FX with explicit fallbacks
  • Position-level translation and weighting
  • Carry and duration aggregation
  • Deterministic stress classification

Requirements

  • Async worker runtime for FX_RATE
  • Network access for live EUR/USD and GBP/USD rates
  • No rules or surface runtime

Expected checkpoint

A known state for this walkthrough.

After both FX requests reach terminal failure without a satisfactory cached value, so the authored 1.08 EUR/USD and 1.25 GBP/USD fallbacks apply. These source-derived values are not yet backed by a runtime receipt; the current contract certifies provider terminal status only.

Portfolio market value
877,044H9, fallback-rate path
Annual carry
46,513.91I9, fallback-rate path
Weighted duration
4.392505J9; duration-heavy
200 bp stress
-77,048.41 / +77,048.41K9 / L9
Portfolio mix
concentrated / core-carryM2 / M3
01 · Acquire

Keep provider work separate from valuation policy

N1 and N2 remain raw FX_RATE bindings. Position rows apply DEFAULT at the point of use, so an operator can inspect whether EUR and GBP values are live, stale, failed, or using the authored continuity rate.

02 · Translate

Make every position explain its USD value

Each row retains notional, price, currency, and effective FX before computing market value and carry. A changed provider result therefore invalidates only the positions that consume that currency.

03 · Weight

Aggregate after the denominator is explicit

H9 establishes total market value; each J-row then contributes its market-value weight times duration. The apparent forward reference is a dependency edge, not an evaluation-order instruction.

04 · Stress

Publish amounts and classifications together

The two 200-basis-point shocks expose portfolio P&L while M2:M4 turn concentration, carry, and duration thresholds into readable decisions. Keep both layers so a classification always has an auditable numeric basis.

06-portfolio-risk-engine.grid
Get Grid
MODEL "Portfolio Risk Engine"
DESCRIPTION "Large portable example for portfolio translation, weighting, carry, and duration stress."
VERSION "1.0.0"
AUTHOR "Grid Team"
TAGS "canonical", "portable", "large", "risk"

# FX inputs
N1 = FX_RATE("EUR", "USD")
N2 = FX_RATE("GBP", "USD")

# Positions: name, notional, price, duration, coupon, currency, fx, mv_usd, carry, dur_contrib, up_200bp, down_200bp
A1 = "UST2Y"
B1 = 150000
C1 = 1.00
D1 = 1.8
E1 = 0.031
F1 = "USD"
G1 = 1
H1 = ROUND(B1 * C1 * G1, 2)
I1 = ROUND(H1 * E1, 2)
J1 = ROUND(H1 / H9 * D1, 6)
K1 = ROUND(H1 * (1 - D1 * 0.02), 2)
L1 = ROUND(H1 * (1 + D1 * 0.02), 2)

A2 = "UST10Y"
B2 = 210000
C2 = 0.99
D2 = 7.2
E2 = 0.042
F2 = "USD"
G2 = 1
H2 = ROUND(B2 * C2 * G2, 2)
I2 = ROUND(H2 * E2, 2)
J2 = ROUND(H2 / H9 * D2, 6)
K2 = ROUND(H2 * (1 - D2 * 0.02), 2)
L2 = ROUND(H2 * (1 + D2 * 0.02), 2)

A3 = "CorpA"
B3 = 90000
C3 = 1.02
D3 = 4.1
E3 = 0.057
F3 = "EUR"
G3 = N1 DEFAULT 1.08
H3 = ROUND(B3 * C3 * G3, 2)
I3 = ROUND(H3 * E3, 2)
J3 = ROUND(H3 / H9 * D3, 6)
K3 = ROUND(H3 * (1 - D3 * 0.02), 2)
L3 = ROUND(H3 * (1 + D3 * 0.02), 2)

A4 = "CorpB"
B4 = 125000
C4 = 0.96
D4 = 5.3
E4 = 0.064
F4 = "EUR"
G4 = N1 DEFAULT 1.08
H4 = ROUND(B4 * C4 * G4, 2)
I4 = ROUND(H4 * E4, 2)
J4 = ROUND(H4 / H9 * D4, 6)
K4 = ROUND(H4 * (1 - D4 * 0.02), 2)
L4 = ROUND(H4 * (1 + D4 * 0.02), 2)

A5 = "MBS"
B5 = 80000
C5 = 1.01
D5 = 3.7
E5 = 0.049
F5 = "USD"
G5 = 1
H5 = ROUND(B5 * C5 * G5, 2)
I5 = ROUND(H5 * E5, 2)
J5 = ROUND(H5 / H9 * D5, 6)
K5 = ROUND(H5 * (1 - D5 * 0.02), 2)
L5 = ROUND(H5 * (1 + D5 * 0.02), 2)

A6 = "LoanA"
B6 = 60000
C6 = 0.98
D6 = 2.5
E6 = 0.071
F6 = "GBP"
G6 = N2 DEFAULT 1.25
H6 = ROUND(B6 * C6 * G6, 2)
I6 = ROUND(H6 * E6, 2)
J6 = ROUND(H6 / H9 * D6, 6)
K6 = ROUND(H6 * (1 - D6 * 0.02), 2)
L6 = ROUND(H6 * (1 + D6 * 0.02), 2)

A7 = "LoanB"
B7 = 72000
C7 = 0.94
D7 = 3.1
E7 = 0.083
F7 = "GBP"
G7 = N2 DEFAULT 1.25
H7 = ROUND(B7 * C7 * G7, 2)
I7 = ROUND(H7 * E7, 2)
J7 = ROUND(H7 / H9 * D7, 6)
K7 = ROUND(H7 * (1 - D7 * 0.02), 2)
L7 = ROUND(H7 * (1 + D7 * 0.02), 2)

A8 = "HY_ETF"
B8 = 50000
C8 = 1.03
D8 = 4.8
E8 = 0.058
F8 = "USD"
G8 = 1
H8 = ROUND(B8 * C8 * G8, 2)
I8 = ROUND(H8 * E8, 2)
J8 = ROUND(H8 / H9 * D8, 6)
K8 = ROUND(H8 * (1 - D8 * 0.02), 2)
L8 = ROUND(H8 * (1 + D8 * 0.02), 2)

# Portfolio summaries
H9 = ROUND(SUM(H1:H8), 2)
I9 = ROUND(SUM(I1:I8), 2)
J9 = ROUND(SUM(J1:J8), 6)
K9 = ROUND(SUM(K1:K8) - H9, 2)
L9 = ROUND(SUM(L1:L8) - H9, 2)
M1 = MAX(H1:H8) / H9
M2 = M1 > 0.22 THEN "concentrated" ELSE "balanced"
M3 = I9 / H9 > 0.06 THEN "high-carry" ELSE I9 / H9 > 0.04 THEN "core-carry" ELSE "low-carry"
M4 = J9 > 4 THEN "duration-heavy" ELSE "duration-light"
M5 = `mv_usd={H9} carry={I9} duration={J9}`
M6 = `up={K9} down={L9} mix={M2}`

END MODEL