Products
Risk Analytics
Risk Analytics proposes index hedges for SPY, QQQ and IWM from live dealer positioning: protective puts when dealers are short gamma and protection is cheap, collars when calls are rich relative to puts, and a note when the volatility term structure is inverted. It also computes empirical historical Value at Risk and expected shortfall for a portfolio you send.
How the proposals are made
/api/v3/institutional/hedging reads the latest trading day's options signals for each of the
three index ETFs and applies three rules. Every number in a proposal is a raw value from that day,
not a model estimate:
hedge_type | Fires when | action |
|---|---|---|
protective_puts | dealer_gamma_net is below 0 (dealers short gamma, so moves are amplified) and iv_rank_pct is below 25 (protection is cheap) | buy index puts |
collar | risk_reversal_25d is above 0 (25-delta calls rich relative to puts) | sell calls to finance protective puts |
term_structure_note | front implied volatility is above 30-day implied volatility (backwardation) | note only: a stress regime, where hedges cost more |
When no rule fires, proposals is an empty list and note says no hedge trigger active; the
per-index metrics are still in market_context. An empty list is an answer, not an error. The
proposals do not know your positions: they describe the index hedge market, not a hedge sized
for a portfolio.
Results are cached for 5 minutes. Each proposal and each market_context entry carries as_of,
the trading date of the signals it used; check it rather than the response timestamp.
Access
5 credits per call, Pro and up.
Endpoints
| Method | Path | MCP tool | Description |
|---|---|---|---|
| GET | /api/v3/institutional/hedging | index_hedge_proposals | Index hedge proposals (SPY/QQQ/IWM): protective puts and collars from dealer gamma, IV rank and the 25-delta risk reversal, plus IV term-structure context. No parameters. |
| POST | /api/ | none (REST only) | Historical VaR and expected shortfall for an explicit USD equity portfolio you send; see Historical VaR. |
| GET | /api/ | kelly_uncertainty | Kelly fraction for one ticker from its stored prediction, in decimals, capped at ±25%. Withheld (null) while the expected return is not a calibrated forecast or the book is more than two sessions old; see Kelly sizing. Billed as Quant Signals (Starter+, 3 credits). |
Example
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/institutional/hedging" # Illustrative response structure; values are placeholders, not live data:# {# "ok": true,# "timestamp": "2026-09-24T14:10:00Z",# "kind": "index_hedge_proposals",# "hedge_set": ["SPY", "QQQ", "IWM"],# "proposals": [# {# "instrument": "QQQ",# "hedge_type": "collar",# "action": "collar: sell calls to finance protective puts",# "rationale": "25-delta risk reversal positive (+0.0120) ... collar financing is attractive",# "dealer_gamma_net": ...,# "dealer_gamma_z": ...,# "iv_rank_pct": ...,# "risk_reversal_25d": 0.012,# "term_structure_signal": ...,# "as_of": "2026-09-23"# }# ],# "market_context": {# "SPY": {# "dealer_gamma_net": ..., "dealer_gamma_z": ..., "iv_rank_pct": ...,# "risk_reversal_25d": ..., "term_structure_iv0": ..., "term_structure_iv30": ...,# "term_structure_signal": ..., "term_structure_state": "contango",# "as_of": "2026-09-23",# "dealer_greeks": {"date": "2026-09-23", "call_gamma": ..., "put_gamma": ...,# "gamma_net": ..., "call_vanna": ..., "put_vanna": ...,# "call_charm": ..., "put_charm": ...}# },# "QQQ": {"...": "..."},# "IWM": {"...": "..."}# }# }If no signals are available for any of the three indices, there is no answer: the route returns
503 with status: "unavailable_ and warning: "no_, and
the call consumes no credits. It means the signals are missing, not that no hedge is needed.
Kelly sizing
/api/ sizes one ticker from its latest stored ensemble
prediction (the book). When it serves a size, it computes in decimal returns, from the prediction's
return estimate and 90% interval, both stored in percent:
mu_decimal = predicted_return_pct / 100sigma_decimal = (interval_half_width / 1.645) / 100kelly_uncapped = mu_decimal / sigma_decimal^2 x convictionkelly_fraction = kelly_uncapped, capped at -0.25 and +0.25Before 2026-09-25 the route divided a percent by a percent squared, so every fraction it served was 100 times too small.
The size is withheld today. kelly_fraction is null when either condition below holds, and
withheld_reasons lists each one that does:
withheld_reasons entry | Meaning |
|---|---|
magnitude_unsupported | The expected return is predicted_return_pct, which is the blended score × 100, not a calibrated forecast, so no size can be derived from it. The same rule withholds predicted_return_pct itself on Quant Signals. magnitudes_withheld names the withheld fields (kelly_fraction, inputs) and the reason, carries the live measurement's direction, measured_ic, horizon_days and blockers, and names what to use instead in use_instead (rank, percentile, decile). |
book_stale | The book is more than two US equity sessions old (stale_after_sessions: 2). |
No stored return is a calibrated forecast yet, so every ticker with a complete prediction answers
with magnitude_unsupported. A withheld answer also sets inputs to null, because its return
estimate is the unsupported number, keeps cap, and repeats the first reason in warning. It is a
refusal, not a size of zero: do not substitute a size of your own.
Every answer for a ticker in the book says how old the book is: as_of_ts (the prediction's own
timestamp), book_age_sessions (US equity sessions after that date, up to and including today),
is_stale and stale_after_sessions.
Response for AAPL on 2026-09-25. kelly_fraction, inputs, withheld_reasons,
magnitudes_, book_age_sessions and is_stale are the captured values; the other
fields are shown as the route builds them, and ... marks values that were not captured:
{ "ok": true, "timestamp": "...", "ticker": "AAPL", "kelly_fraction": null, "warning": "book_stale", "as_of_ts": "...", "book_age_sessions": 9, "is_stale": true, "stale_after_sessions": 2, "inputs": null, "cap": 0.25, "withheld_reasons": ["book_stale", "magnitude_unsupported"], "magnitudes_withheld": { "fields": ["kelly_fraction", "inputs"], "reason": "the expected return is blended_score*100, not a calibrated forecast, so no size can be derived from it", "use_instead": ["rank", "percentile", "decile"], "...": "..." }, "method": "fractional_kelly_uncertainty_discounted"}When a size is served, the response adds kelly_uncapped and cap_binding (true when the cap
cut the size), and inputs holds mu_pct and sigma_pct in percent (mu and sigma_estimate
are the same two values under their earlier names), mu_decimal, sigma_decimal, conviction,
interval_half_width_pct and units.
Other null answers name their cause in warning:
ticker_(the ticker has no stored prediction) ornot_ in_ predictions no_predictions(no prediction table was available). These carry no book age.- A fund or ADR names its cause in an
excludedblock instead ofwarning(error_), with no book age: its row carries an equity-model score for characteristics a fund or ADR does not have, so no size is derived from it (see Funds and ADRs).code: "excluded_ from_ equity_ ranking" no_predicted_return,no_interval_half_widthorno_conviction: the stored prediction lacks that input. Nothing is substituted for it; the route used to put a fixed volatility of0.02in place of a missing interval, and a conviction of0.5in place of a missing one.
Every answer, withheld or not, is a 200 and is billed. Answers are cached for 60 seconds.
A Kelly fraction is a research input, not an instruction to trade.
Historical VaR for a portfolio you send
POST / computes empirical historical Value at Risk and
expected shortfall. You supply the positions, their market values and their daily return
history; the API runs non-overlapping historical scenarios over that history and returns the loss
at your chosen confidence and horizon. It reads no account: the numbers describe the portfolio in
your request.
The route is served on REST. It is not an MCP tool, and the public OpenAPI spec does not list it
yet. GET /api/capabilities carries its full request schema under
tengu_.
The request
The body has four top-level fields:
| Field | Required | What it is |
|---|---|---|
portfolio | yes | snapshot_id, as_of, nav_usd, cash_usd, and 1 to 20 positions, each with a permno, a side (long or short) and its market_value_usd |
history | yes | The dated daily total returns for each position, which the scenarios are built from |
confidence | no | 0.90 to 0.99, default 0.95 |
horizon_sessions | no | 1 to 30 trading sessions, default 1 |
The calculation needs at least ten tail observations at the chosen confidence and horizon. With fewer, the route refuses rather than estimating.
Reading the result
The response states the VaR and expected shortfall in USD, the scenario window and its age, and where each input came from. It does not verify that the positions are held anywhere, it does not calibrate future coverage, and it is not an instruction to trade.
Handle unavailable output
Inspect ok, status, and any availability or warning fields before using a
numeric risk field. A successful HTTP exchange can carry an unavailable result.
Do not use the response timestamp as the time of the underlying market data.
See API reference for error handling and
pricing and credits for settlement behavior.