stress_portfolio
Reprice your entire portfolio across market shocks to see the P&L curve and its trough, identifying the worst-case loss and the market move that triggers it.
Instructions
Reprice the whole portfolio — options, equities, futures — across underlying shocks and return the P&L curve and its trough.
The trough is the primary output: the worst point of the curve and the
shock at which it sits. troughRefined interpolates between grid points
for where the minimum actually falls, and is labelled as interpolated.
The model, returned with every result in assumptions:
only equity underlyings are on the axis by default (
scope='equity'). FX, rates and anything else is excluded outright and listed underexcludedwith its market value. This is what Risk Navigator's Equity tab does, and it is what makes the two comparable — verified against a live account, where the engine and Risk Navigator agreed to 7 dollars on 29,000 at a 15% fall once the FX leg was off both. Off the equity axis the single shock is meaningless: the same account's CAD strangle was contributing -21,716 at -20% and -7,183 at +10%, dominating both tails.all underlyings in scope are shocked by the same percentage at once, which is Risk Navigator's own default assumption.
betasscales that shock per symbol and reaches every class that responds to one — an option is repriced at its own beta-scaled move, not at the index move. Use it to stand a foreign underlying down off an equity axis, and read the warning it produces: a beta of 0 takes a position off this curve, it does not measure that position's own risk.options are repriced with Black-76 on the shocked forward using IB's implied volatility. Equity options are carried from spot using IB's pvDividend, so both kinds go through one pricer.
the volatility level is flat along the shock axis unless you say otherwise. Neither vol mode raises it: sticky_strike pins volatility to the strike, sticky_moneyness slides a strike along today's smile. Real volatility rises when an index falls, and a net short option book pays for that on top of the delta and gamma this curve already counts.
vol_bumpdoes not fill the gap — it is constant across shocks.vol_slope_downdoes: 1.0 adds one volatility point per 1% fall. It is your input, not a measurement, and it is applied as a parallel shift across every tenor.P&L is model-price-now against model-price-shocked, so the curve is exactly zero at zero shock by construction. The gap between the local model and IB's own price is reported per position as
modelVsMarketinstead of being folded into the curve. Expect it to be small out of the money and a couple of percent in the money, where IB prices the early exercise that Black-76 has no room for — so the curve understates losses slightly once options go deep in the money.bonds are held flat unless
bond_rate_shift_bpis set; anything this server does not model is held flat and named inwarnings.breakdown='expiry'addspnl_by_expiryto every point andtroughByExpirybeside the trough — each expiry's own worst point and what it contributes at the portfolio's trough. On a book holding one root across many expiries those are the two different numbers behind "which short is the problem", andpnl_by_symbolcan answer neither.
Check reconciled before quoting anything. At zero shock the portfolio
is rebuilt from its positions and compared against NetLiquidation; a
residual over 1% returns reconciled: false with the residual attached. A
curve that does not reconcile is missing something, and the number it gives
for the trough is missing it too.
Risk Navigator's own volatility shock model is not published. sticky_strike
is the approximation that corresponds to its default curve, not a
reproduction of it — expect the shape to match and the last few percent
not to.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| betas | No | Per-symbol share of the shock, e.g. {'AAPL': 1.2, 'EUR': 0.0}. Applies to options and futures as well as equities: the beta scales the move of that position's own underlying, and the option is then repriced there. Key it on the root ('ES'), the local symbol ('ESZ6 P5800') or the underlying ('ESZ6'); the most specific match wins. Use it to stand a foreign underlying down off an equity axis — but read the warning it produces: a beta of 0 removes a position from this curve, it does not measure its own risk. | |
| scope | No | Which underlyings are on the shock axis. 'equity' — the default — keeps only equity underlyings and excludes FX, rates and the rest outright, which is what TWS Risk Navigator's Equity tab does and what makes the curve comparable to it. Off the equity axis a single percentage shock is meaningless: a currency future moved 20% prices an exchange rate that has never traded there. 'all' shocks everything alike. Excluded positions are always listed under `excluded`, never dropped in silence. | equity |
| shocks | Yes | Underlying moves as fractions: -0.10 is a 10% fall. A range like -0.30 to +0.30 in 0.01 steps is the usual ask. | |
| vol_bump | No | Added to every volatility, in points, flat along the shock axis: 0.05 is +5 points at every shock. For volatility that responds to the shock itself, use vol_slope_down. | |
| vol_mode | No | sticky_strike keeps each strike's current volatility and is what Risk Navigator's default curve does. sticky_moneyness slides the smile with the forward. | 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 sticky_moneyness pull neighbouring strikes from IB when the portfolio holds too few to define a smile. Costs extra market data requests. | |
| risk_groups | No | Override the risk group of a symbol, e.g. {'TLT': 'rates', 'GLD': 'metals'}. IB publishes no asset class for a bond or gold ETF quoted as a stock, so those are classified as equity unless named here. Groups: equity, fx, rates, metals, energy, other. | |
| default_beta | No | ||
| vol_slope_up | No | Volatility points removed per 1% RISE. Positive means volatility falls as the market rallies, which is the usual direction. Separate from vol_slope_down because the response is not symmetric. | |
| valuation_date | No | Value the curve at this ISO date, e.g. '2026-09-30', instead of counting the days out by hand into date_offset_days. Mutually exclusive with it. No calendar magic: the date is the date, weekend or not, and time to expiry is ACT/365 throughout. The P&L is still measured FROM today at today's prices and volatilities, with the clock rolled forward — it is decay, not a forecast. | |
| vol_slope_down | No | Volatility points added per 1% FALL in the underlying: 1.0 means a -20% shock reprices at +20 points. Zero — the default — holds the volatility level flat, which prices the move in the underlying and not the move in volatility that comes with it. A net short option book loses real money on that term, so leaving this at zero is the optimistic half of the answer. | |
| date_offset_days | No | Roll the valuation date forward this many days (time decay). | |
| bond_rate_shift_bp | No | Parallel rate shift in basis points applied to bonds. Zero leaves them unchanged, which is the default. | |
| bond_duration_years | No | Duration assumed for bonds when a rate shift is applied. IB does not publish duration, so this is your input and the result is only as good as it. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||