stress_curve
Plot portfolio P&L across volatility scenarios in a single call, comparing constant-volatility and vol-coordinated assumptions to expose the shock that breaks the book.
Instructions
The portfolio P&L curve under several volatility regimes at once — the data behind a risk graph, for plotting rather than for reading point by point.
Risk Navigator draws two curves: a constant-volatility line and one from its
own implied-volatility model, which is not documented and cannot be
reproduced. This returns as many as you ask for, and the volatility
assumption behind each is a number in the output rather than a black box:
volSlopeDown is volatility points per 1% fall.
Read the slope-0 curve first. It is the constant-volatility case and the only one with an external check — it should sit close to Risk Navigator's blue line. If it does not, the volatility lookup is wrong and no other scenario in the result means anything.
Check volSurfaceUsed. It lists every quote the repricing actually
read, as (underlying, tenor, strike, iv). Empty under sticky_moneyness
means no expiry held three strikes, so no smile could be built and every
option silently fell back to sticky_strike — the result looks perfectly
normal and is not the model you asked for. fetch_skew=true fixes it at the
cost of extra market data requests.
How each curve is built, and where it is weakest:
Every scenario reprices one loading of the portfolio and one surface, so the curves differ by assumption alone. Calling the single-curve tool three times could not promise that: the book moves between calls.
The starting volatility is IB's own, per contract, out of a model that prices American exercise. The surface is used only for the change in volatility as a strike slides to new moneyness, which keeps IB's better number as the anchor and keeps every curve exactly zero at zero shock.
vol_coordreproduces IB's own model: volatility is multiplied, not shifted — a fall of X moves it by 10X relatively, a rise by -X, damped across tenors. Being relative is what makes the surface steepen by itself, since a wing already quoted at 41% takes more points than a 31% at-the-money out of the same scenario. The asymmetry is IB's documented one; the damping is fitted here and is not published, sovol_coord_decayis an input you should refit against your own Risk Navigator.The additive
volSlopeDownalternative is a parallel shift, flat across tenors. It cannot steepen at any value, and on a ratio book that is the difference between a curve that keeps falling and one that turns back up. Prefervol_coordunless you specifically want a flat regime.Equities move by the shock times their beta, futures and options by their own underlying's beta-scaled move. Bonds are flat unless
bond_rate_shift_bpis set. FX is off the axis by default and reported underexcludedwith its market value — not held flat in silence.Options IB would not model are repriced from a locally implied volatility where a mark price exists, flagged per position and in
warnings, and held flat only when even that fails.date_offsets=[0, 3](orvaluation_dates) crosses the scenarios with a family of valuation dates, so "today against Monday, when the August wings expire" is one call rather than two that the market moves between. Each entry undercurvescarries its ownvaluationDateanddateOffsetDays;namestays the scenario's andlabeldistinguishes them. Time is the only thing that moves — today's spot and today's volatilities with the clock advanced, which is decay and not a forecast.breakdown='expiry'putspnl_by_expiryon every point andtroughByExpiryon every curve, keyed on the option's settlement date. On a book running one root across many expiries that is the only way to see which expiry owns the trough;pnl_by_symbolshows one "ES" number for all of them.
pnl_pct_of_nlv is on every point, and netLiquidation at the top. Quote
the fraction rather than the amount when comparing two dates or two
accounts. Check reconciled before quoting any of it.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| betas | No | Per-symbol share of the PRICE shock — unrelated to a scenario's volatility slope. Scales the move of that position's own underlying, options and futures included. | |
| scope | No | Which underlyings are on the shock axis. 'equity' excludes FX, rates and the rest outright and lists them under `excluded`; 'all' shocks everything by the same percentage, which off the equity axis is meaningless. | equity |
| shocks | No | Underlying moves as fractions: -0.20 is a 20% fall. Defaults to -0.40 to +0.10 in 2% steps, which is wide enough on the downside that a short-gamma trough falls inside the window rather than on its edge. | |
| vol_mode | No | sticky_strike — the default — pins each strike to the volatility it holds today. This is what Risk Navigator's blue curve does, and it is the only setting under which the slope-0 curve can be checked against it: measured on a live index ratio book the two agree to within 1-3% at every shock from 0 to -30%. sticky_moneyness instead rereads each strike's volatility at the moneyness it lands on after the shock, off the portfolio's own surface, interpolated across strike and expiry — a defensible model, but a different one, and on that same book it deepened the trough by a factor of 1.8. Do not compare it to Risk Navigator. | sticky_strike |
| breakdown | No | Which per-point P&L breakdowns to return. 'symbol' — the default and what this server has always returned — groups by root, which on an options book collapses every ES expiry under one 'ES' key. 'expiry' groups by the option's SETTLEMENT date instead ('ES 2026-10-30'), which is the breakdown that answers 'which expiry is holding the trough down, and which short do I buy back'. 'both' returns the two of them and 'none' neither. Positions with no expiry get a key naming their class ('ES (future)', 'AAPL (equity)') so the breakdown still sums to the point's total and can be checked against it. Responses are large: a book on nine expiries pays for the second dictionary at every one of twenty-six shocks, so ask for 'both' only when you want both, and 'none' when you only want the curve. | symbol |
| fetch_skew | No | Let the surface pull neighbouring strikes from IB for expiries the portfolio holds too thinly. Costs extra market data requests, and is what to reach for when `volSurfaceUsed` comes back thin or empty. | |
| risk_groups | No | Override a symbol's risk group, e.g. {'TLT': 'rates'}. IB publishes no asset class for a bond or gold ETF quoted as a stock. | |
| date_offsets | No | A FAMILY of valuation dates in one call, as day offsets: [0, 3] is today and three days out. Every scenario is run at every offset, and all of them come out of ONE loading of the positions and prices — which is what makes the comparison mean anything, since calling this tool twice lets the book and the market move between the two answers. Mutually exclusive with date_offset_days and valuation_dates. | |
| default_beta | No | ||
| vol_scenarios | No | One curve per volatility regime. Defaults to the two curves Risk Navigator itself draws: 'const' (no volatility response, its blue line) and 'vol_coord' (IB's own volatility-coordinated model). Always keep a const curve in the set — it is the one that can be checked against Risk Navigator, and if it does not line up nothing else in the result is worth reading. Additive slopes are still there for a regime you want to state by hand, but prefer vol_coord: a slope shifts the surface in parallel, which on a book that is short the middle and long both wings is the wrong shape and not merely the wrong size. | |
| valuation_dates | No | The same family, given as ISO dates: ['2026-08-28', '2026-08-31']. Mutually exclusive with the offset forms. No calendar magic — the date is the date, weekend or not. | |
| vol_coord_decay | No | Term damping of the vol_coord model: VR(t) = exp(-decay * t), so a front-month contract takes nearly the whole shock and a back month a fraction. IB documents that this function exists and is decreasing but not what it is, so this number is FITTED, not published. Left unset it uses your own stored calibration if calibrate_vol_coord has ever been run, and otherwise the factory fit of 4.736 — one number calibrated on one book, which has no claim on yours. `assumptions.volCoordDecaySource` says which of the three you got. | |
| date_offset_days | No | Roll the valuation date forward this many days (time decay). | |
| bond_rate_shift_bp | No | ||
| bond_duration_years | No | ||
| vol_coord_calibrated_to_years | No | How far out in tenor `vol_coord_decay` was actually constrained. Defaults alongside the decay: your calibration's reach if you have one, and otherwise 0.345, the four months the shipped fit was constrained over. An exponential extrapolates to zero, which would price a one-year option as carrying no volatility risk at all in a crash, so positions past this are priced anyway and named in `warnings` rather than passing in silence. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||