HKUDS/Vibe-Trading · error · ValueError

S and K must be > 0, got S={S}, K={K}

Error message

S and K must be > 0, got S={S}, K={K}

What it means

The Black-Scholes formula requires a strictly positive spot S and strike K (log-normal dynamics need log(S/K)). implied_volatility enforces this before building no-arbitrage bounds, because a non-positive S or K makes both the pricing formula and the intrinsic-value interval mathematically meaningless.

Source

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

        from 0.05 to 0.60, so a solver that answers there is reporting the
        arbitrary endpoint of its own search, not a market volatility. This
        function refuses that: it checks vega at the candidate solution and
        returns ``nan`` when the price carries no volatility information. That
        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

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Audit the argument order and the source columns feeding S and K.
  2. Drop or quarantine rows with S <= 0 or K <= 0 before the vol solve.
  3. Assert positivity at data ingestion with a clear error message including the contract ID.

Example fix

# before
iv = implied_volatility(px, row['close'], row['volume'], T, r, 'put')  # volume in K!

# after
assert row['close'] > 0 and row['strike'] > 0
iv = implied_volatility(px, row['close'], row['strike'], T, r, 'put')
Defensive patterns

Strategy: validation

Validate before calling

assert S > 0 and K > 0, f'need positive S={S}, K={K}'

Type guard

def are_positive_levels(*vals) -> bool:
    return all(isinstance(v, (int, float)) and v > 0 for v in vals)

Try / catch

try:
    iv = implied_volatility(px, S, K, T, r, option_type)
except ValueError as e:
    if 'S and K must be > 0' in str(e):
        quarantine(contract_id, reason='bad levels')
    else:
        raise

Prevention

When it happens

Trigger: Calling implied_volatility with S=0 or K=0 (or negatives); passing a null/NaN spot from a failed market-data lookup that was coerced to 0.0.

Common situations: Missing prices defaulting to 0 in a feed join; strike loaded as 0 for a forward or a contract row that is not a vanilla option; DataFrame columns misordered so a volume or bid column lands in K.

Related errors


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