HKUDS/Vibe-Trading · error · ValueError

market price {market_price} is below intrinsic value {lower}

Error message

market price {market_price} is below intrinsic value {lower}

What it means

implied_volatility first checks the quote against no-arbitrage bounds: the discounted intrinsic value is the lower bound (within tolerance tol). A market price below intrinsic is arbitrageable and cannot be matched by any volatility, so the function refuses instead of returning sigma ~ 0 or a nonsense negative vol.

Source

Thrown at agent/src/quantlib/options.py:449

        is a property of the quote rather than a solver failure, and no
        price-tolerance method can do better -- but a confident wrong number is
        worse than an admitted absence.

    Raises:
        ValueError: If ``option_type`` is invalid, if ``T``, ``S`` or ``K`` is
            non-positive, or if ``market_price`` lies outside the no-arbitrage
            interval, which includes the intrinsic-value violation
            ``market_price < discounted intrinsic``.
    """
    option_type = normalise_option_type(option_type)
    if T <= 0:
        raise ValueError(f"T must be > 0 to imply a volatility, got {T}")
    if S <= 0 or K <= 0:
        raise ValueError(f"S and K must be > 0, got S={S}, K={K}")

    lower, upper = _no_arbitrage_bounds(S, K, T, r, option_type, q)
    if market_price < lower - tol:
        raise ValueError(
            f"market price {market_price} is below intrinsic value {lower}"
        )
    if market_price >= upper:
        raise ValueError(
            f"market price {market_price} is at or above the no-arbitrage "
            f"ceiling {upper}; no implied volatility exists"
        )

    def identified(candidate: float) -> float:
        """Return the candidate only if the quote actually pins it down.

        The test is whether one volatility point of movement shifts the price by
        more than the tolerance the solve was run to. If it does not, then a
        whole band of volatilities reprices within ``tol`` and whichever one the
        search happens to land on is an artefact of the search, not a reading of
        the market. Comparing vega against an absolute floor cannot express
        this, because the threshold has to scale with ``tol``.

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Verify r, q, S, K, and option_type are all correct for this contract.
  2. If the quote is stale, refresh data or skip the contract.
  3. If the shortfall is within tolerance, raise tol — but only after confirming it is a rounding-level discrepancy.

Example fix

# before
iv = implied_volatility(market_price=4.90, S=100, K=95, T=0.01, r=0.0, option_type='call', tol=1e-8)  # intrinsic ~5 -> raises

# after
iv = implied_volatility(4.90, 100, 95, 0.01, 0.0, 'call', tol=0.25)  # explicit, documented slack
Defensive patterns

Strategy: validation

Validate before calling

S_disc = S * math.exp(-q * T) if option_type == 'call' else K * math.exp(-r * T)
assert market_price >= intrinsic_lower_bound - tol  # recompute or use doc'd bound

Type guard

def quote_above_intrinsic(price: float, S: float, K: float, put: bool) -> bool:
    return price >= (max(0.0, K - S) if put else max(0.0, S - K)) - 1e-9

Try / catch

try:
    iv = implied_volatility(px, S, K, T, r, option_type)
except ValueError as e:
    if 'below intrinsic' in str(e):
        skip_quote(quote_id, reason='sub-intrinsic')
    else:
        raise

Prevention

When it happens

Trigger: Quoting 3.0 for a 100/120 call with S=100 (intrinsic 0) is fine, but quoting 0.5 for a 100/95 call near expiry (intrinsic ~5) triggers it; also stale prices where the spot moved but the option quote did not, or a wrong dividend rate q inflating the computed lower bound.

Common situations: Crossed/stale quotes in market data snapshots; using the wrong interest rate or dividend yield so the computed intrinsic exceeds the quote; mixed up bid/ask sides or price multiplied by the wrong contract multiplier.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/6429ccf2b2d76bca. Report an issue: GitHub.