ibkr-risk-mcp
It is an MCP server for Interactive Brokers portfolio risk analysis that pulls IB model greeks, volatility surfaces, and what-if margin, then rebuilds P&L stress curves locally.
Check TWS/Gateway connectivity and diagnose failure modes.
Get per-segment margin, liquidity, and available-funds summaries.
Retrieve IB model greeks for every option position, with settlement-date handling and missing-data reporting.
Fetch IB implied volatility surfaces for an underlying across expiries and strikes.
Stress the full portfolio across underlying shocks and return the P&L curve, its trough, and reconciliation checks.
Stress under multiple volatility regimes and valuation dates in one call via stress_curve.
Add hypothetical legs and see their effect on the stress curve and trough via stress_whatif.
Evaluate IB margin impact of hypothetical structures with whatif_order, gated and read-only-safe.
Calibrate and persist the volatility-coordinated decay model for later stress runs.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ibkr-risk-mcpWhere is the trough of my portfolio's P&L curve?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ibkr-risk-mcp
MCP server exposing Interactive Brokers' portfolio risk: IB's model greeks, IB's implied volatility surface, IB's what-if margin, and a local stress engine that rebuilds the P&L-versus-underlying curve and finds its trough.
It deliberately does not duplicate the official IBKR connector. Positions, balances, orders, trades, performance, allocation, spot and historical prices, option chains, watchlists and alerts all come from there. This server exists to fill the one gap that connector leaves — risk analysis — and nothing else.
The questions it is built to answer:
where is the trough of the portfolio's P&L curve across underlying shocks, at constant volatility?
and how much of that answer is the constant-volatility assumption itself?
if I add N puts at strike K expiring E, where does that trough move to?
how much margin does this hypothetical structure need, now and under stress?
what does IB's volatility surface look like for this underlying?
Risk Navigator's risk model is not exposed by any API, so the strategy is to pull the inputs from IB — per-contract implied volatility, model greeks, what-if margin — and rebuild the curves locally, rather than trying to read Risk Navigator itself.
Prerequisites
TWS or IB Gateway running and logged in on this machine. The server talks to its local socket; it never reaches IBKR over the internet.
The API enabled. File → Global Configuration → API → Settings → tick Enable ActiveX and Socket Clients. Until you do, TWS opens no port at all and
check_connectionreportsnot_listening.Market data for the instruments you hold. Model greeks come from IB's own option model, so a contract the account cannot price has no greeks and is reported under
missing. Contrary to what is widely repeated, delayed data does carry model greeks — verified against live TWS, where an unsubscribed account got nothing from market data types 1 and 2 and implied volatilities from type 3. If you lack the subscription, setIBKR_MARKET_DATA_TYPE=3and read the numbers as a quarter of an hour old.For
whatif_orderonly: Read-Only API must be off in that same TWS screen. That setting blocks what-if orders too.
Default ports: 7496 TWS live, 7497 TWS paper, 4001 Gateway live, 4002 Gateway paper. If the configured one is dead, check_connection scans all four and tells you which is answering.
Related MCP server: ibkr-mcp-server
Install
{
"mcpServers": {
"ibkr-risk": {
"command": "uvx",
"args": [
"--from",
"https://github.com/simoneb/ibkr-risk-mcp/archive/refs/tags/<TAG>.tar.gz",
"ibkr-risk-mcp"
],
"env": {
"IBKR_PORT": "7496",
"IBKR_CLIENT_ID": "17",
"IBKR_ENABLE_WHATIF": "false"
}
}
}
}Client | File |
Claude Desktop |
|
Claude Code |
|
Cursor |
|
VS Code |
|
Codex CLI |
|
Pin a tag rather than a branch, and prefer the archive URL over git+https://… — the git form needs git on PATH, and some clients hand the server too small an environment to find one.
For a local checkout:
uv venv && uv pip install -e ".[dev]"
uv run python -m ibkr_risk_mcp.serverAs a Claude Desktop extension, manifest.json surfaces host, port, client id, account, market data type, risk-free rate and the what-if gate as a settings form, so none of them need editing by hand.
Environment variables
Variable | Default | |
|
| Where TWS listens. A remote host must also be in the API's Trusted IPs |
|
| 7496/7497 TWS live/paper, 4001/4002 Gateway live/paper |
|
| Must differ from every other script on this TWS. Never 0 — TWS reserves that for orders placed by hand |
| — | Required on a multi-account login. Without it every position/account tool refuses rather than combining accounts |
|
| 1 live, 2 frozen, 3 delayed, 4 delayed-frozen. 3 works and does carry greeks |
|
| Seconds to wait for a what-if reply. IB sometimes never sends one |
|
| The gate on |
|
| Used to discount and to carry spot to the forward in the local repricing |
|
| Seconds to wait for greeks on one contract. Short by design — IB answers fast or never, and explicit refusals cut the wait short anyway |
|
| Concurrent market data subscriptions. IB allows about 50 |
|
| Seconds for the API handshake |
|
| Where |
Copy .env.example to .env for local runs.
Tools
Tool | |
| Is TWS reachable, and if not, which of the four failure modes it is |
| NetLiq, margin requirements, available funds and excess liquidity — per segment |
| IB's model greeks for every option position, with both expiry dates |
| IB's implied volatility grid for an underlying, by expiry and strike |
| The P&L curve across underlying shocks, and its trough |
| The same curve under several volatility regimes and valuation dates at once — risk-graph data, with the vol assumption as a visible parameter |
| The same curve with hypothetical legs added: base, with-legs, and the difference |
| Refit the volatility-coordinated model against your own Risk Navigator, and keep the fit |
| IB's margin impact of a structure, per leg and cumulatively. Needs the gate below |
Each tool carries the protocol's annotations, so a client can group them by permission. Seven are marked read-only. whatif_order is not, because it puts something on IB's order channel even though nothing is routable; calibrate_vol_coord is not either, because it writes the fit to disk — neither of them changes anything in the account.
Reading the curve: by symbol, by expiry, at another date
Every stress result carries the P&L broken down per shock. By default that breakdown is keyed on the symbol, which is the right unit for a book of many underlyings and the wrong one for a book running one underlying across many expiries: nine ES expiries all land under a single ES key, and the operative question — which expiry is holding the trough down, and which short do I buy back — has to be reconstructed by hand from the position list.
breakdown changes the key. expiry groups on the option's settlement date (ES 2026-10-30), so a quarterly and a weekly that settle the same morning are one row rather than two names for one expiry. 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 rather than trusted. both returns symbol and expiry, none neither, and symbol remains the default: the responses are already large, and a second dictionary at every one of twenty-six shocks is not free.
With an expiry breakdown the result also carries troughByExpiry, two columns per expiry that answer two different questions:
pnl— that expiry's own worst point along the curve, which is what it can cost.pnlAtPortfolioTrough— what it contributes at the shock where the account's floor actually sits, which is what says whether closing it would move that floor.
They come apart, and the gap is the useful part. An expiry whose own minimum sits at −35% while the book troughs at −22% is not the one to buy back, and reading only the first column would nominate it.
Valuation dates. date_offset_days rolls the clock forward; valuation_date takes the ISO date instead, so "the curve at 30 September" does not have to be counted out by hand over a weekend. There is no calendar adjustment — the date is the date — and both forms mean the same thing: the P&L is still measured from today, at today's spot and today's implied volatilities, with time advanced. That is decay and the change in convexity that comes with it, not a forecast of where the market will be.
stress_curve takes a family of them, date_offsets: [0, 3] or valuation_dates: [...], and crosses them with the volatility scenarios. That is not a convenience: comparing today against Monday used to take two calls, and the book and the market moved between them, so part of the difference between the two curves was not the three days at all. One call, one loading of positions and prices, and time is the only thing that changed. Each entry under curves carries its own valuationDate and dateOffsetDays; name stays the scenario's and label distinguishes the pair.
The what-if gate
whatif_order works only with IBKR_ENABLE_WHATIF=true. Otherwise it sends nothing and returns success: false with blocked: true.
An order carrying whatIf=True is evaluated by IB's margin engine and discarded — never routed, never acknowledged as live, never in the order book. The gate exists anyway, for two reasons. It is the only thing in this server that touches the order path at all, so being read-only should be provable rather than asserted; and with the gate closed the ib_async connection itself is opened in read-only mode, which makes an order impossible below this server as well as inside it.
No tool here can submit a live order. Those belong somewhere else.
The traps this server handles
These are not hypothetical; each one produces a confidently wrong number if you skip it.
Expiry dates are reported twice, and AM settlement does not always move the date. For AM-settled contracts — the quarterly ES options, trading class ES — some TWS builds report lastTradeDateOrContractMonth as the day before settlement, so an 18 December expiry shows as the 17th while a PM weekly expiring the same morning shows as the 18th; time to expiry taken from the last trading day is then a day short.
But that is not universal, and assuming it is would introduce the error it was meant to prevent. Measured on TWS server 178: the December ES quarterly reports 20261218 — the correct date — with the AM settlement visible only in lastTradeTime='08:30:00' against the weekly's 15:00:00. IB's own ContractDetails.realExpirationDate exists for exactly this ambiguity and is preferred wherever the details have been fetched; the class-and-date heuristic is only the fallback. Every tool returns both lastTradeDate and settlementDate plus an amSettled flag, and everything downstream uses settlement.
The underlying is not implied by the expiry — and two options expiring the same morning need not share one. Confirmed against live TWS: the 18 December 2026 ES quarterly is written on ESZ6, while the EW3 weekly expiring that same day is written on ESH7. The 30 September end-of-month options are on ESZ6, not ESU6. underConId comes from the contract details and is never inferred from a date.
Portfolio contracts arrive unqualified. The contracts on IB's PortfolioItem come back with exchange empty, and reqMktData on a contract without an exchange returns nothing at all — no ticks, no error. This is why a live book returned no model greeks at all for any option position while a surface request on the same underlying worked perfectly: those contracts had come from reqContractDetails already qualified. Every position is requalified before its data is requested.
impliedVol and undPrice arrive in separate ticks. Taking the greeks the moment a volatility appears leaves the underlying unset a good fraction of the time — a sizeable minority of positions on a measured live run — and without a forward there is nothing to reprice against, so the position silently drops out of the curve. Both fields are waited for, and a missing forward is backfilled from another position on the same underlying before it is fetched again.
A refusal is an answer; don't wait it out. IB replies to a market data request in about a second or not at all, and when it is "not at all" it usually says so at once with error 354 or 10091. Those are watched for and end the wait immediately. Without that, an unentitled book pays the full timeout on every contract — the difference between a check taking 30 seconds and taking minutes.
Where IB won't publish greeks, the price is already in the portfolio. Error 10091 — "requires additional subscription for API" — refuses the option's greeks and a quote on its underlying, so the obvious fallback of implying a volatility locally dies for want of a spot price. But a book holding GOOGL stock next to a GOOGL option already knows what GOOGL is worth: every position carries its own mark on the portfolio update, which arrives over the account channel and is gated by no market data entitlement at all. Underlying prices are taken from the account's own positions before a market data line is ever spent. Measured on a live account, this is the difference between two long puts priced and the same two held flat across every shock — worth 2,826 at a 15% fall and 4,414 at 20%, on a 138k account. A long option contributes a multiple of its premium under shock, so judging it by its market value understates it badly.
reqCalcImpliedVolatility can take the connection down, so it is opt-in. Asking IB to run its own American-exercise model on prices you supply is the better answer than implying a European volatility locally — when it works. Measured against live TWS: ib_async's request is answered with error 320, "Error reading request. Please use 'Key=Value' format for Misc Options", and TWS then closes the API connection. A protocol error halfway through a portfolio load costs the whole load, which is far worse than the one contract it was trying to rescue. It is behind IBKR_USE_IB_IMPLIED_VOL, off by default, and disables itself for the rest of the process after a single failure. The local implication is stamped with its source and never passed off as IB's own either way.
A bare root is ambiguous. ES is the E-mini S&P 500 future and Eversource Energy on NYSE. With no sec_type the stock wins, and you get a plausible-looking volatility surface with 75-dollar strikes. Every resolution reports what else the symbol matched, and get_vol_surface returns the contract it actually used.
IB requires transmit=True on a what-if order. With transmit=False — the intuitive choice for something meant not to trade — TWS rejects it with error 321 and, because it rejects rather than answers, the call never returns at all. whatIf=True is what keeps the order off the market; transmit has nothing to do with it. Every what-if here is also bounded by a timeout, because IB not answering is an ordinary outcome.
The volatility level does not move with the shock unless you make it. Both vol modes decide which volatility a strike gets, not how high the surface sits: sticky_strike pins each strike to the volatility it holds today, sticky_moneyness slides a strike along the smile the portfolio already has. Neither raises the level, and vol_bump cannot either — it is flat along the shock axis by construction. So the default curve prices the move in the underlying and none of the move in volatility that comes with it, which for a net short option book is the optimistic half of the answer and can be the larger half. vol_slope_down puts it in: 1.0 adds one volatility point per 1% fall, so a −20% shock reprices at +20 points. It is applied as a parallel shift across every tenor — a real surface also steepens in a sell-off, and a 120-day volatility moves less than the front month — so it is an input to be chosen and stated, in the same class as bond_duration_years. Expect it to change the shape of the curve and not only its depth: on the measured fixture it deepens the −10% and −20% points while lifting the −30% tail, because the long wings pick up vega faster than the short body does.
Volatility has a term structure. ES at 139 days can sit near 15% at the money while the front month prints 12%. Using one ATM number for every tenor understates a long-dated position badly. The surface interpolates per tenor, in total variance rather than in volatility, which is also what keeps it free of calendar arbitrage.
Bonds are quoted as a percentage of nominal. 50,000 nominal at 97.85 is worth 48,925, not 4,892,500 — quantity × price is wrong by 100×. Position values come from IB's own marketValue, and the reconciliation below is what proves it worked.
Futures have no market value to add. Variation margin settles daily, so a future contributes its unrealised P&L to NetLiquidation, not its notional. Adding the notional instead moves the total by a quarter of a typical account.
Two accounts must never be added together. ib.portfolio("") and ib.accountValues("") do not mean "the default account", they mean all of them. On a multi-account login with no IBKR_ACCOUNT set, that would combine two portfolios and reconcile the total against one account's NetLiquidation — a wrong answer wearing the shape of a right one. Every tool that reads positions or account values refuses until an account is chosen, and names the candidates. Related: IB permits only one reqAccountUpdates subscription at a time, so asking about a second account silently cancels the first; only the resolved account is ever subscribed.
Reconciliation. stress_portfolio rebuilds the portfolio at zero shock — cash, plus the securities' market value, plus the futures' unrealised P&L — and compares it against NetLiquidation. A residual over 1% returns reconciled: false with the residual attached. Nothing derived from a portfolio that does not reconcile should be presented as fact.
A minimum at the edge of the range is not a trough. If the curve is still falling at −30% the engine says so, rather than reporting the boundary as the worst case. Some portfolios simply keep losing past the edge of the window.
Known limitations
Risk Navigator's volatility shock model is not public. It is not exposed by any API and IB does not document it. sticky_strike — each strike keeps its current implied volatility — is the approximation corresponding to Risk Navigator's default blue curve. Expect the shape to match and the last few percent not to. Do not present a number from this server as "what Risk Navigator says"; it is what a documented model, fed IB's own volatilities, says.
One shock, all underlyings — and only the equity ones. Every underlying in scope is moved by the same percentage at once, which is Risk Navigator's own default assumption. There is no correlation matrix and no per-underlying scenario.
Because of that, scope defaults to equity: only equity underlyings are on the axis, and FX, rates and the rest are excluded outright and listed under excluded with their market value. This is not a refinement, it is what makes the number mean anything — one percentage applied to every underlying at once is nonsense off the equity axis, where a 20% shock on a currency future prices an exchange rate that has never traded there. On a live account a single CAD strangle was contributing −21,716 at −20% and −7,183 at +10%, against −29,027 and +2,408 for an entire ES campaign: it dominated both tails of a curve that was supposed to be about equities. TWS Risk Navigator draws the same line in its Equity tab, and once it is drawn here too the two curves agree to 34 dollars on 29,000 at a 15% fall. scope='all' restores the old behaviour.
The classification is a table plus one heuristic — a three-letter currency code on a FUT or FOP is IB's own naming for a currency future — not a deduction, because IB publishes no reliable asset class for futures and a bond or gold ETF quoted as STK lands in equity with no field to say otherwise. So the group is reported on every position and risk_groups overrides it per symbol.
betas scales that shock per symbol, and it reaches every class that responds to one — an option is repriced at its own beta-scaled move of its underlying, with strike, smile and convexity all measured at the forward it would actually reach, rather than having its P&L scaled after the fact. Keys are tried most specific first: local symbol (ESZ6 P5800), then root (ES), then underlying (ESZ6).
This is what lets a foreign underlying be stood down off an equity axis — a short EUR strangle is not a 20%-down position when the S&P falls 20%, and a single shock across every underlying says it is. But read what a beta does and does not do. It scales the underlying's move only: vega and theta are untouched, so a position at beta 0 still contributes P&L the moment vol_bump or date_offset_days is set, and pnl_by_symbol is the only clean exclusion. More importantly, standing a position down is not measuring it — an attenuated strangle carries its whole gap risk and none of that risk is anywhere on the curve. Every run that applies a beta other than 1 says so in warnings.
Volatility surface interpolation is local. Under sticky_moneyness the surface is built from the strikes the portfolio actually holds — one skew per expiry that has at least three of them, assembled into a surface and interpolated across tenors in total variance. An expiry too thin to define its own slope borrows its shape from the tenors that do; an underlying where no expiry defines one falls back to sticky_strike rather than being handed an invented flat smile. Either way it is said in warnings. fetch_skew=true pulls neighbouring strikes from IB instead, at the cost of more market data requests.
The surface supplies the change in volatility as a strike slides to new moneyness, not the level. The level stays IB's own per-contract implied volatility, which comes out of a model that prices American exercise and is a better number than any fit through it. Reading the level off the surface would also break the curve's zero: a strike whose shape was borrowed from another tenor would not get its own volatility back at zero shock. Every result carries volSurfaceUsed, the quotes the repricing actually read — if it is empty under sticky_moneyness, no smile was built, every option silently fell back to sticky_strike, and the result is not the model you asked for.
The volatility response is IB's own model, and both curves are validated against Risk Navigator. Risk Navigator draws two lines: a constant-volatility curve, and one it labels Vol.Coord. where volatility moves as a deterministic function of the price shock. IB documents that second model's shape — the nominal shock is -X on a rise and -10X on a fall, applied relatively rather than in points, then damped across tenors by a response function VR(t) that is 1 at zero and decreasing. vol_coord implements it. Measured against a live index ratio book, on a −30% to 0% axis:
RMS against Risk Navigator, as a fraction of trough depth | |
| ~2% |
| ~3.5% |
with the residual at every shock inside the error of reading the targets off a chart by eye. VR(t) itself is not published: vol_coord_decay is fitted here, exp(-4.736 t), on one book from nine points read off a chart by eye. Two things follow, and the engine says both out loud rather than leaving them in the docs.
Every vol_coord curve running on the shipped decay says so in warnings. And the fit was constrained only out to 0.345 years, because that is all the book it came from held; past there an exponential does not merely lose accuracy, it decays to nothing. At one year VR is 0.009, so this model would reprice a LEAPS as though a 20% crash barely touched its volatility. Any position beyond vol_coord_calibrated_to_years is priced anyway and named in warnings, because a silent extrapolation that understates long-dated vega is exactly the failure this server exists not to have. A floor on VR would tidy the symptom away and hide it, so there isn't one.
To replace the number rather than trust it, call calibrate_vol_coord with four or more readings off Risk Navigator's own Vol.Coord. curve on its Equity tab — {shock: -0.20, pnl: -28000}, shocks as fractions. The same fit is available from a shell:
uv run python scripts/calibrate_vol_coord.py -- -0.05=-8000 -0.10=-22000 -0.15=-31500 -0.20=-28000 -0.25=-12000Either route refits the decay and returns the residual at every point, the tenor range your positions actually constrain, and the most extreme volatility the fit produces — a decay that reproduces the curve by pricing a wing at 150% has fitted the chart rather than the market, and it tells you so.
The fit is kept. It goes to ~/.ibkr-risk-mcp/vol_coord.json (IBKR_CALIBRATION_FILE moves it) and becomes the default vol_coord_decay for every later stress_curve on that machine, no restart and nothing to carry by hand — which was the actual reason the shipped number kept being the one in use. Stored beside it is what it was fitted against: the targets, the residuals, the account, the date. A calibrated run then reports that provenance in assumptions.volCoordDecaySource and in warnings instead of the "factory decay" caveat, and a decay you pass explicitly is described as neither — this server did not fit it and does not vouch for it.
A fit taken against a portfolio that does not reconcile is returned but never stored. The asymmetry is the point: a curve missing a position announces itself through reconciled, while a decay that absorbed the same gap would go on deforming every later run with nothing to give it away.
Being relative is the part that matters, and it is why the additive slopes were removed from the defaults. Multiplying every volatility by the same factor puts more points on a wing already quoted at 41% than on a 31% at-the-money, so the surface steepens by itself. A parallel shift in points cannot do that at any slope, and on a ratio book the difference is not a matter of degree: the additive model made the curve monotonically worse through the region where Risk Navigator turns it back up, and put its crossover around −35% where Risk Navigator puts it near −18%. Measured there, vol_coord troughs at roughly 60% of the depth of the constant-volatility curve and at a much shallower shock — so a rising-volatility regime came out as the better one, the opposite of what a naive short-vega reading predicts. That is the reason the regimes are returned as separate curves rather than as a band.
The additive slope alternative is your input, not a measurement. Neither vol mode moves it: sticky_strike pins volatility to the strike and sticky_moneyness slides a strike along today's smile, so with both slopes at zero the curve prices the move in the underlying and not the move in volatility that comes with it — the optimistic half of the answer for a net short option book. vol_slope_down puts it in, at volatility points per 1% fall. It is applied as a parallel shift, flat across tenors, and a real surface does neither: it steepens in a sell-off, which understates a long out-of-the-money put, and the front month moves more than a 120-day tenor. A steepening term was tried and removed — the values that reproduced the observed shape priced the long wings above 100% implied volatility, which is curve fitting rather than modelling.
stress_curve exists because of that. Rather than burying one regime in one result, it returns a curve per regime over a single loading of the portfolio and a single surface, so the curves differ by assumption alone — by default the same two Risk Navigator draws. Read the slope-0 curve first: it is the constant-volatility case and the only one with an external check against Risk Navigator's blue line — and it passes. Measured against a live index ratio book, stress_curve at vol_mode='sticky_strike', slope 0, tracks Risk Navigator's blue curve to within 1-3% at every shock from 0 to −30%. That check only works in sticky_strike, which is why it is the default here; sticky_moneyness is a different model and on the same book put the trough 1.8x deeper. And do not assume the steepest slope is the worst case everywhere — on a book holding long wings the ordering reverses in the far tail, where the least-deep long puts carry the most vega and a rising volatility starts helping.
The local repricing is European; most of these options are American. Black-76 has no early exercise, while equity options and CME futures options both do. The gap is negligible out of the money and real once an option is in the money. Measured against live IB data: a 75-strike put with spot at 71.94 priced at 4.51 locally against IB's 4.65, a 2.9% shortfall that is the early-exercise premium and nothing else. It is reported per position as modelVsMarket rather than hidden, and it means the curve slightly understates losses deep in the money.
Rates are an input, not a measurement. IB does not publish the rate behind its own model. IBKR_RISK_FREE_RATE moves option values by little over the horizons this server deals with, but it is not zero either, and it is part of the modelVsMarket residual above.
Currency. Everything is summed in the account's base currency as IB reports it. A portfolio with positions IB values in another currency will show up as a reconciliation residual rather than being converted.
No exchange holiday calendar. The AM-settlement shift moves to the next weekday. An expiry the day after a holiday would be off by one, and that shows in settlementDate rather than hiding.
Tests
uv run pytest -q # unit tests, no TWS needed
uv run python scripts/smoke_test.py # end-to-end, needs TWSThe unit tests run the whole repricing layer against recorded JSON fixtures in tests/fixtures/, with the valuation date pinned, so a pricing bug is distinguishable from a market data problem and the numbers do not drift as time passes. The fixture portfolio holds an AM-settled quarterly and a PM-settled weekly on the same morning, a future, an equity and a bond — every trap above, in one file.
scripts/smoke_test.py exercises the live path: connection, greeks with a count of any missing modelGreeks, a surface, stress_portfolio from −30% to +30% in 1% steps with the reconciliation check, stress_curve over its three default regimes — checking both that the constant-volatility curve starts at zero and that volSurfaceUsed is not empty — stress_whatif, and a whatif_order on a single deeply out-of-the-money leg. Point .env at the paper port to try it safely.
Layout
src/ibkr_risk_mcp/
server.py MCP tool definitions — the docstrings are the interface
connection.py ib_async lifecycle, one connection, four failure modes
marketdata.py greeks, vol surface, rate limiting, portfolio snapshot
pricing.py Black-76 / Black-Scholes, skew and surface interpolation
stress.py the stress and what-if engine
margin.py whatif_order and the segmented margin summary
contracts.py expiry and underlying normalisation
calibration.py where the fitted vol_coord decay is stored between sessions
scripts/smoke_test.py
scripts/calibrate_vol_coord.py
tests/pricing.py and contracts.py have no IB dependency at all, which is what makes them testable.
Available Tools
9 toolscalibrate_vol_coordA
Fit vol_coord_decay to YOUR Risk Navigator, and keep the result.
vol_coord reproduces IB's volatility-coordinated model. Its asymmetry — a
fall moves volatility ten times as hard as a rise — is IB's, documented.
Its term damping VR(t) is not: IB says only that the function exists and
is decreasing. The number this server ships was fitted to one screenshot
of somebody else's Risk Navigator, from nine points read off a chart by
eye. It has no claim on your book, every vol_coord result says so in
warnings, and this tool is how you replace it.
What to do:
In TWS open Risk Navigator's risk graph, on the Equity tab if you use the default scope.
Read the Vol.Coord. curve — the one that responds to volatility, not the constant-volatility line — at four or more shocks spread across the range you care about.
Pass them in as
{shock: -0.20, pnl: -28000}pairs, shocks as fractions.
The fit comes back with the residual at every point and, more usefully, with what to distrust about it: the tenor range your positions actually constrain, and the most extreme volatility the fitted decay produces. A decay that reproduces the curve by pricing a wing at 150% has fitted the chart rather than the market, and it says so rather than leaving you to find out three layers down in a P&L.
Unless persist=false, the fit is written to disk and becomes the default
vol_coord_decay for every later stress_curve on this machine — no
restart, no carrying the number by hand — together with what it was fitted
against, which is then reported in assumptions.volCoordDecaySource. Set
IBKR_CALIBRATION_FILE to move the file. It is the only thing this server
writes.
A fit taken against a portfolio that does not reconcile is returned but
not stored. The reason is asymmetry of failure: a curve that is missing
a position announces itself through reconciled, while a decay that
absorbed the same gap would go on silently deforming every vol_coord run
afterwards.
Nothing here trades or quotes. It reads positions and reprices locally.
| Name | Required | Description | Default |
|---|---|---|---|
| betas | No | ||
| scope | No | Match the Risk Navigator tab the readings came from. Its Equity tab is 'equity', the default. | equity |
| persist | No | Store the fit as this machine's standing calibration, so every later stress_curve uses it without being told. Set false to see the fit without adopting it. A fit against a portfolio that does not reconcile is never stored, whatever this says. | |
| targets | Yes | Four or more points read off Risk Navigator's Vol.Coord. curve, spread across the range you care about. Three is the minimum the fit will accept and it constrains almost nothing; points bunched near the money constrain nothing at all. Get them from the Equity tab if you run the default scope='equity', so the curve you are fitting to excludes FX and rates the same way this engine does. | |
| vol_mode | No | Match the run you intend to use the decay in. sticky_strike is the default everywhere else and is what Risk Navigator's own curve does. | sticky_strike |
| fetch_skew | No | ||
| risk_groups | No | Same override as elsewhere, e.g. {'TLT': 'rates'}. Use the same one you pass to stress_curve, or the fit is against a different book than the runs that will use it. | |
| default_beta | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses the critical side effect: unless persist=false, the fit is written to disk and becomes the standing calibration, and it names this as 'the only thing this server writes.' It also explains the asymmetric failure behavior around unreconciled portfolios and notes that nothing trades or quotes. This goes well beyond the structured annotations and adds genuinely important operational context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but earns its length: it front-loads the core purpose, uses bold to mark the most important caveats, and organizes content into workflow, persistence, and failure-mode sections. There is no filler, and every paragraph contributes either to correct invocation or to preventing misuse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex calibration tool with eight parameters, the description covers the full invocation workflow, persistence semantics, failure conditions, and safety boundaries. It also references assumptions.volCoordDecaySource and the returned residuals, so with an output schema present, the agent has enough to call the tool correctly and interpret results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description substantially enriches the meaning of key parameters: targets must be four or more points spread across the range, shocks are fractions, persist=false means trial-only, and risk_groups should match stress_curve. However, three parameters (betas, fetch_skew, default_beta) have no schema description and are not explained in the description either, leaving a small but real gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Fit `vol_coord_decay` to YOUR Risk Navigator, and keep the result.' It clearly distinguishes this calibration tool from siblings like stress_curve by explaining that the fitted decay becomes the default for all later stress_curve calls. The purpose is unmistakable and not a tautology.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'What to do' section gives a numbered, actionable workflow, including where to read the curve in TWS, how many points to collect, and how to pass them. It also states when a fit is not stored (unreconciled portfolio) and how persist=false changes behavior. This is explicit, practical guidance an agent can follow directly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
check_connectionARead-onlyIdempotent
Check whether TWS or IB Gateway is reachable and an account is logged in.
Call this first whenever another tool fails. It separates the four situations that all present as "cannot connect" and need different fixes:
not_listening— nothing is on the port. TWS is not running, or IBKR_PORT points at the wrong one; the response lists which of the four default ports (7496 TWS live, 7497 TWS paper, 4001/4002 Gateway) are answering.api_not_enabled— the port answers but the API handshake never completes. "Enable ActiveX and Socket Clients" is off in TWS.client_id_in_use— another script holds this client id.not_logged_in— the API is up but no account is loaded.connected— everything is in place.
hint says what to do about the state in each case.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as read-only and idempotent. The description adds substantial behavioral context beyond that, such as the meaning of each connection state, the specific default ports checked, and the content of the `hint` field. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: a one-sentence purpose, a clear 'when to use' directive, and a bulleted list of states with concise explanations. Each sentence adds value and is front-loaded with the most critical information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no parameters and an output schema present, the description fully covers what an agent needs to know: the purpose, when to call, the possible states, and what `hint` provides. Nothing crucial is missing for a diagnostic tool of this complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the schema coverage is trivially 100%. The description correctly focuses on behavior and output rather than parameters. A baseline of 4 is appropriate since no parameter clarification is needed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Check whether TWS or IB Gateway is reachable and an account is logged in.' It also enumerates the five specific states it distinguishes, making the tool's role unmistakable and differentiating it from siblings that handle trading or risk operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs to 'Call this first whenever another tool fails,' which is direct usage guidance. It also explains how to interpret each state and what `hint` provides, giving the agent clear context for when and how to use the tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_margin_summaryARead-onlyIdempotent
Margin and liquidity, split by segment.
Returns NetLiquidation, EquityWithLoanValue, FullInitMarginReq,
FullMaintMarginReq, AvailableFunds, ExcessLiquidity, TotalCashValue,
BuyingPower and Leverage — each as the account total and, where IB reports
it, separately for the securities (-S) and commodities (-C) segments.
The segments are the point. Futures margin must be met in the commodities segment; IB covers a shortfall there by sweeping cash out of the securities segment. An account whose total excess liquidity looks healthy can still be one bad day away from a forced liquidation if the shortfall lands in commodities while securities is also falling. Report the segment figures, not only the totals, whenever futures are involved.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safe-read nature is covered. The description adds valuable behavioral context beyond annotations: IB may sweep cash from securities to commodities, and a healthy total excess liquidity can still mask a segment-level shortfall. This teaches the agent how to interpret the returned numbers rather than just their type.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is somewhat long but each sentence earns its place: a crisp opening, a field list, and a bolded explanation of why segments matter. The formatting is clean with a lead sentence, an em-dash separated detail, and a clear call to action.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With zero parameters, an output schema present, and annotations covering safety, the description goes beyond the minimum by explaining the key subtlety of segment-level margin and how to act on it. The tool is fully contextualized for the agent, including a warning scenario and a reporting directive.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the input schema carries no burden; the baseline is 4. The description does not need to clarify parameters and instead focuses on explaining the output's segment semantics, which is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Margin and liquidity, split by segment,' which clearly names the resource (margin and liquidity) and the specific scope (segment breakdown). It then enumerates the exact fields returned, distinguishing this tool from siblings like stress_portfolio or get_position_greeks.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells the agent when to emphasize segment figures: 'Report the segment figures, not only the totals, whenever futures are involved.' It also explains why segments matter by describing the commodities-segment shortfall and forced-liquidation risk, giving the agent actionable context for choosing how to use the results.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_position_greeksARead-onlyIdempotent
IB's model greeks for every option position — its numbers, not ones implied locally.
Returns per position: conid, symbol, secType, right, strike, lastTradeDate, settlementDate, daysToExpiry, position, multiplier, undPrice, impliedVol, delta, gamma, vega, theta, optPrice and pvDividend.
Things to know about the values:
undPrice is the forward IB used, not the index spot. For a futures option that is the future's price, which differs from the cash index by the basis; any repricing has to start from it.
multiplier comes from the contract. ES is 50 and MES is 5, and the difference is a factor of ten in every exposure figure.
settlementDate, not lastTradeDate, is the expiry. The quarterly ES options settle AM and stop trading the afternoon before, so TWS shows 17 December for something that expires on the 18th, while a weekly settling the same morning shows the 18th. Both are returned; pair positions on settlementDate.
Missing greeks are listed, not dropped. A contract IB never published model greeks for appears under
missingwith the reason. The usual causes are no market data subscription for that instrument, or a strike too illiquid for IB to imply a volatility. Note that delayed data does carry model greeks, so a missing row is rarely explained by the market data type alone.
| Name | Required | Description | Default |
|---|---|---|---|
| symbol | No | Restrict to options on this underlying root, e.g. 'ES'. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false. The description adds substantial behavior beyond these: undPrice is the forward IB used, multiplier is from the contract, settlementDate is the actual expiry, and missing greeks are listed rather than dropped. It also hints at data subscription requirements, providing rich behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is lengthy but structured and front-loaded with the core purpose, followed by a return field list and a bulleted list of key nuances. Every sentence carries substantive information, and the bullet format improves readability without padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with an output schema and one well-documented optional parameter, the description is complete. It explains critical interpretation details (forward vs spot, multiplier scaling, settlement date vs last trade date, and missing-row behavior) that are not derivable from the schema alone. It fully covers the tool's context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 100% description coverage for its single optional parameter ('symbol'), with a clear description. The tool description adds no additional parameter-specific meaning, so the baseline of 3 applies because the schema already handles parameter semantics adequately.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'IB's model greeks for every option position.' It clearly distinguishes the tool from siblings, such as get_vol_surface, by noting these are 'its numbers, not ones implied locally.' This makes the tool's purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context for when to use this tool—when broker model greeks are needed, not local implied ones—and explains caveats like settlement date and missing greeks. However, it does not explicitly mention sibling alternatives or state when not to use the tool, so it stops short of a full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_vol_surfaceARead-onlyIdempotent
IB's implied volatility surface for an underlying: a grid of (expiry, strike) with impliedVol, delta, optPrice, undPrice and daysToExpiry.
This is the input that makes local repricing deterministic. With IB's own volatilities in hand, a constant-volatility scenario needs no proprietary model — only Black-76 arithmetic on top of numbers IB published.
Do not collapse the surface to one number. Volatility has a term structure: ES at 139 days can sit near 15% at the money while the front month prints 12%. Using the front month for a longer tenor understates a long-dated position badly. Read the tenor you need, and interpolate between tenors in total variance if you must.
daysToExpiry counts to the settlement date. For AM-settled expiries
that is one day past the last trading date TWS shows.
Strikes with no published volatility come back under missing with the
reason rather than being silently absent — a surface missing its left wing
looks identical to one that has none.
Check which instrument you got. A bare root is ambiguous: ES is the
E-mini S&P 500 future and Eversource Energy on NYSE, and with no
sec_type the stock wins. The resolved contract comes back under
underlying, any collision is listed in warnings, and a surface on the
wrong instrument looks perfectly reasonable until you notice the strikes
are two orders of magnitude off.
| Name | Required | Description | Default |
|---|---|---|---|
| rights | No | Puts by default; pass both for the full smile. | |
| strikes | No | Explicit strikes. Each is snapped to the nearest listed one. | |
| expiries | Yes | Expiries as YYYYMMDD or YYYY-MM-DD. Either the last trading date or the settlement date works; both resolve to the same contracts. | |
| sec_type | No | Which instrument the symbol means. Left empty the search tries STK, then IND, then FUT — so 'ES' returns Eversource Energy, not the E-mini S&P. Pass FUT for a futures root. Any collision is reported in `warnings` either way. | |
| max_strike | No | ||
| min_strike | No | ||
| underlying | Yes | Underlying root, e.g. 'ES' or 'SPY'. | |
| trading_class | No | Needed when one underlying has two contracts expiring the same day, e.g. 'ES' (quarterly, AM-settled) against 'EW4' (weekly, PM-settled). | |
| max_strikes_per_expiry | No | Cap per expiry. Each strike is one market data line and IB allows about fifty at once, so a wide grid over many expiries takes time. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds substantial behavioral context beyond the readOnly/idempotent/non-destructive annotations: missing strikes appear under 'missing' with reasons, daysToExpiry counts to settlement date (not just trading date), and instrument-root ambiguity is resolved via 'underlying' and 'warnings'. These are valuable behavioral traits not inferable from annotations alone, and there is no contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every section earns its place, with bolded headings breaking up distinct concepts (deterministic repricing, don't collapse, settlement dates, missing strikes, instrument ambiguity). It is front-loaded with the core definition and then layers critical warnings. No filler or redundant phrasing.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 9 parameters and an output schema, the description covers the essential contextual pitfalls: settlement vs trading date, missing strike handling, root-symbol collisions, and usage cautions about term structure. The output schema exists, so return values need no elaboration. The tool is complex and the description fully equips the agent with the needed context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 78%, so the schema already explains most parameters. The description adds meaningful context for several: it explains sec_type disambiguation with the 'ES' example, trading_class resolution for same-day expiries, strike snapping behavior, and the rate-limit implication of max_strikes_per_expiry. This goes beyond the schema's dry definitions, though not exhaustively for all parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns IB's implied volatility surface for an underlying, with a grid of (expiry, strike) and specific fields. It uses a specific verb ('get') and resource ('volatility surface'), and the detailed behavior distinguishes it from sibling tools dealing with margin, greeks, stress, and orders.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Extensive usage guidance is provided: it explains when to use the surface for local repricing, warns against collapsing the surface to a single number, advises reading the correct tenor and interpolating in total variance, and tells users to check the underlying instrument to avoid ambiguity. It gives clear context and practical 'how to use' directions, though it doesn't name alternative tools explicitly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
stress_curveARead-onlyIdempotent
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.
| 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 |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, open-world, and non-destructive behavior, and the description adds substantial beyond-annotation detail: silent fallback from sticky_moneyness to sticky_strike, fitted unpublished decay parameters, bonds held flat unless a shift is set, FX reported under excluded rather than silently ignored, and the explicit warning to check reconciled before quoting results. No contradiction exists.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but every section earns its place: it front-loads the core purpose, then organizes verification, silent fallbacks, curve construction, valuation-date families, and breakdowns under clear bolded lead-ins. The depth is proportional to a 16-parameter analytical tool, and there is no tautology or padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description does not need to enumerate return fields, yet it still covers the critical ones: curves, valuationDate, dateOffsetDays, pnl_by_expiry, excluded, warnings, netLiquidation, and reconciled. It also explains verification, model provenance, and calibration state, making it complete for safe invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is high at 81%, but the description still adds real meaning: volSlopeDown is volatility points per 1% fall, vol_coord is multiplicative rather than shifted, date_offsets creates one consistent family of valuations, and breakdown='expiry' keys on settlement date. It turns parameter names into decision-relevant concepts rather than mere labels.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific deliverable: the portfolio P&L curve under several volatility regimes, positioned as the data behind a risk graph for plotting. It distinguishes itself from the single-curve sibling by emphasizing that this tool guarantees one loading of the portfolio across all scenarios.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit operational guidance: read the slope-0 curve first as the external check, prefer vol_coord unless a flat regime is specifically wanted, use fetch_skew when volSurfaceUsed is thin, and use breakdown='expiry' to see which expiry drives the trough. It also names alternatives and failure conditions, leaving little to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
stress_portfolioARead-onlyIdempotent
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.
| 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 |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the tool read-only, idempotent, and non-destructive. The description goes far beyond that, disclosing model internals: Black-76 repricing, flat volatility assumptions, how troughRefined interpolates, how reconciled is computed, what happens to excluded and unmodeled positions, and the modelVsMarket gap. This is exemplary transparency for a complex analytical tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but front-loaded with the core purpose and primary output, then organized into clear behavioral bullets. Each sentence carries meaningful caveats, though some points (e.g., beta semantics, scope semantics) restate or extend schema text, adding a bit of redundancy. Still, it is dense and purposeful rather than padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity — 15 parameters, multiple model assumptions, a reconciliation check, and a detailed output shape — the description is remarkably complete. It explains not only what is returned but how to interpret troughRefined, reconciled, excluded, warnings, pnl_by_expiry, and modelVsMarket. It also warns about known limitations relative to Risk Navigator, which an agent needs before quoting results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is already high at 93%, so the baseline is 3. The description adds substantial meaning on top: betas scale the shock per symbol and remove positions at zero; scope='equity' matches Risk Navigator's Equity tab; vol_slope_down is a user input, not a measurement; breakdown='expiry' returns each expiry's trough contribution. Nearly every parameter gains operational context beyond its schema description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Reprice the whole portfolio — options, equities, futures — across underlying shocks and return the P&L curve and its trough.' This clearly distinguishes the tool's scope. However, it does not explicitly contrast itself with sibling tools like stress_curve or stress_whatif, so it stops short of full sibling differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives strong situational guidance: when to use equity scope, when betas are appropriate, when to set vol_slope_down, when breakdown='expiry' answers a different question, and when to trust the trough only if reconciled. It stops short of explicitly saying 'use this instead of stress_curve when...' or naming excluded conditions, so no direct alternative routing is provided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
stress_whatifARead-onlyIdempotent
The same stress run, with hypothetical legs added — three curves and three troughs: the portfolio as it stands, the portfolio plus the legs, and the difference.
This is what replaces reading Risk Navigator's What-If by hand: "if I add N puts at strike K expiring E, where does the trough move to?"
Read all three troughs. The trough of the difference is not the difference of the troughs — adding protection moves where the worst point sits as well as how deep it is, and comparing only the depths hides the move. A structure that lifts the bottom by very little may still have pushed it from −8% out to −15%, which is the part that matters.
Hypothetical options are priced off IB's current model greeks for those
exact contracts, so both curves start from the same volatilities and the
difference is the structure alone. A leg that cannot be resolved or priced
is reported in legProblems and left out of the second curve; the
comparison then covers only the legs that did resolve, and says so.
With breakdown='expiry' all three curves are broken out by settlement
date, and the difference curve's pnl_by_expiry is where the structure
actually landed: the expiry you traded against moves and the ones you did
not read zero, which is how a hedge is told apart from a change of subject.
A short expiry whose row goes to roughly nothing in withLegs is one the
structure has closed out.
Nothing is sent to IB's order path here — this is pure local repricing. For what the structure costs in margin, use whatif_order.
| Name | Required | Description | Default |
|---|---|---|---|
| legs | Yes | Hypothetical legs to add. Each is either a conid, or symbol+secType+expiry+strike+right, with action BUY/SELL and a quantity. | |
| betas | No | ||
| scope | No | equity | |
| shocks | Yes | Underlying moves as fractions, as above. | |
| vol_bump | No | ||
| vol_mode | No | 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 | ||
| risk_groups | No | ||
| default_beta | No | ||
| vol_slope_up | No | ||
| 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 | ||
| date_offset_days | No | ||
| bond_rate_shift_bp | No | ||
| bond_duration_years | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the operation read-only and idempotent, and the description adds substantial behavioral context: it is pure local repricing with no IB order-path interaction, hypotheticals are priced off current IB model greeks, unresolved legs surface in legProblems and are excluded, and the trough-of-difference caveat is explicitly warned about. It also clarifies how breakdown='expiry' changes what the curves mean.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core output, then proceeds through caveats, pricing behavior, breakdown semantics, and safety in a logical order. It is long but dense with useful information; a few sentences could be tightened, but none feel like padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 16-parameter tool, the description plus the existing output schema and annotations gives an agent enough to understand the primary use case, the required legs and shocks inputs, breakdown behavior, failure reporting, and the safety profile. Advanced parameters remain under-specified, but they have defaults and are not essential to the core scenario the description targets.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 25%, so the description must compensate. It does meaningfully explain legs and breakdown behavior, including unresolved-leg exclusion and pnl_by_expiry interpretation. However, it leaves many parameters such as betas, scope, vol_mode, vol_bump, fetch_skew, risk_groups, and the bond/rate/date parameters effectively unexplained, so the compensation is only partial.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific operation: take the same stress run, add hypothetical legs, and return three curves/troughs (base portfolio, portfolio plus legs, and the difference). It clearly distinguishes this from whatif_order, which is explicitly called out for margin-cost questions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It frames the intended use case directly: answering What-If questions like 'if I add N puts at strike K expiring E, where does the trough move to?' It also gives an explicit exclusion, directing margin-cost needs to whatif_order and noting that no orders are sent. It does not enumerate every sibling, but the key alternative is named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
whatif_orderAIdempotent
IB's own margin impact for a hypothetical structure. Nothing reaches the market.
Each order carries whatIf=True, which IB evaluates in its margin engine
and discards: it is never routed, never acknowledged as live, never appears
in the order book. The tool is still gated behind IBKR_ENABLE_WHATIF=true
because it is the only thing in this server that touches the order path at
all; with the gate closed it sends nothing and returns blocked: true.
Returns initMargin, maintMargin and equityWithLoan before/after/change, plus commission and any warningText, in two views:
perLeg— each leg evaluated on its own.cumulative— legs 1..k as a combo, for every k, so you can see where the offset appears.
Read the combined figure, not the sum of the legs. SPAN offsets the legs
against each other and against what the account already holds, so the two
differ — the difference is reported as offset.spanOffset. IB's what-if on
arbitrary multi-leg combos is unreliable and will sometimes return nothing;
that is reported per step and does not mean the structure is invalid.
If every call fails with no margin figures, check whether TWS has "Read-Only API" enabled — that setting blocks what-if orders too.
| Name | Required | Description | Default |
|---|---|---|---|
| legs | Yes | The structure to evaluate. Each leg is a conid, or symbol+secType+expiry+strike+right, with action BUY/SELL and a quantity. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses crucial behavioral traits: orders are never routed, never acknowledged as live, never appear in the order book, and return blocked when the gate is closed. It also surfaces a reliability caveat about multi-leg combos sometimes returning nothing, plus the Read-Only API failure mode — rich transparency beyond structured hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average, but every section earns its place: purpose, safety, return fields, view breakdown, interpretation warning, and troubleshooting. Bolded key takeaways and a clean bullet list make it scannable without padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with complex margin semantics, the description explains return values (initMargin, maintMargin, equityWithLoan, commission, warningText), two output views, SPAN offset behavior, failure modes, and environment prerequisites. The output schema exists, but the description goes beyond it to give operational context, making it highly complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides full description coverage: legs are explained as conid or descriptive fields with action and quantity. The tool description adds interpretive guidance ('Read the combined figure, not the sum of the legs') but does not meaningfully expand parameter semantics beyond the excellent schema documentation, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description immediately states a specific purpose: 'IB's own margin impact for a hypothetical structure.' It adds a strong differentiator with '**Nothing reaches the market.**' and explains that each order carries whatIf=True, clearly distinguishing it from live-order or simple read-only siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use this tool — for hypothetical structures needing margin impact without market exposure — and includes practical caveats like the IBKR_ENABLE_WHATIF gate and the TWS Read-Only API check. It does not explicitly name an alternative tool or state when not to use it, but the context is clear enough.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
The three stress_* tools and two what-if tools live in the same conceptual neighborhood, so an agent must read carefully to pick between stress_portfolio, stress_curve, stress_whatif, and whatif_order. That said, each has a clearly distinct purpose—underlying-shock P&L, multi-volatility curves, hypothetical-leg P&L, and IB margin impact—and the descriptions draw those boundaries well.
snake_case verb_noun names are the dominant pattern (check_connection, get_margin_summary, stress_portfolio, calibrate_vol_coord), but whatif_order breaks the verb convention and the stress_/get_/whatif_ prefixes are not uniformly aligned. Still, the names are predictable enough to group and search.
Nine tools cover the server's stated risk-analysis scope without redundancy: connection health, margin, greeks, vol surface, stress curves, what-if, and calibration. Each tool occupies a distinct function and none feel like filler.
The risk workflow is well covered: users can check connectivity, inspect margin and greeks, fetch vol surfaces, stress the book, add hypothetical legs, and estimate margin impact. Minor gaps remain—there is no direct non-option position listing and no way to stress an individual underlying without running the whole portfolio—but these are workarounds rather than dead ends.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Hosted MCP for stocks, options, Greeks, brokers, order previews, alerts, and workflows.
Portfolio risk analytics — VaR, Monte Carlo, optimization, options Greeks, stress testing.
Crypto portfolio risk analysis: VaR, scenarios, liquidity and correlation engines as MCP tools.
HPSILab Quant finance MCP for US stocks, ETFs, options, Monte Carlo, backtesting, and risk analysis.
Related MCP Servers
- AlicenseAqualityDmaintenancePortfolio risk analytics MCP server — VaR, Monte Carlo simulation, stress testing, portfolio optimization, options Greeks, and correlation analysis. Real market data via Yahoo Finance. Free tier available, Pro at $29/mo.101162MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for Interactive Brokers that exposes portfolio data, market quotes, trading, and analysis to any MCP-compatible AI client, with support for EU investors and safety-gated trading.1MIT
- FlicenseNot gradedqualityCmaintenanceAn MCP server that turns Interactive Brokers into a question-answering portfolio analyst.4
- FlicenseBqualityCmaintenanceMCP server for Interactive Brokers via IB Gateway, enabling read access to account data and trading capabilities for paper accounts.11
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/simoneb/ibkr-risk-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server