{
 "checks": [
  {
   "id": "textbook",
   "title": "One reference price",
   "family": "Example",
   "caught": 3,
   "score": 0.1875
  },
  {
   "id": "parity",
   "title": "Put-call parity",
   "family": "No-arbitrage",
   "caught": 4,
   "score": 0.25
  },
  {
   "id": "bounds",
   "title": "Price bounds",
   "family": "No-arbitrage",
   "caught": 7,
   "score": 0.4375
  },
  {
   "id": "strike-shape",
   "title": "Falling and convex in strike",
   "family": "No-arbitrage",
   "caught": 6,
   "score": 0.375
  },
  {
   "id": "homogeneity",
   "title": "Doubling spot and strike",
   "family": "No-arbitrage",
   "caught": 0,
   "score": 0.0
  },
  {
   "id": "delta-bump",
   "title": "Delta against a bump",
   "family": "Self-consistency",
   "caught": 9,
   "score": 0.5625
  },
  {
   "id": "gamma-bump",
   "title": "Gamma against a bump of delta",
   "family": "Self-consistency",
   "caught": 5,
   "score": 0.3125
  },
  {
   "id": "vega-bump",
   "title": "Vega against a bump",
   "family": "Self-consistency",
   "caught": 8,
   "score": 0.5
  },
  {
   "id": "implied-round-trip",
   "title": "Implied volatility round trip",
   "family": "Self-consistency",
   "caught": 7,
   "score": 0.4375
  },
  {
   "id": "monte-carlo",
   "title": "Monte Carlo",
   "family": "Independent method",
   "caught": 5,
   "score": 0.3125
  },
  {
   "id": "limits",
   "title": "Zero volatility and expiry",
   "family": "Limits",
   "caught": 5,
   "score": 0.3125
  }
 ],
 "mutants": [
  {
   "id": "d2-time",
   "title": "d2 uses time instead of its square root",
   "why": "Identical when time is exactly one year.",
   "before": "d2 = d1 - vol * sqrt(time)",
   "after": "d2 = d1 - vol * time",
   "failures": {
    "bounds": "S=50.02, K=43.48, r=0.04716, q=0.0679, vol=0.3518, T=0.04529: call=6.33734 outside [6.47855, 49.86466]",
    "strike-shape": "S=50.02, K=43.48, r=0.04716, q=0.0679, vol=0.3518, T=0.04529: butterfly=-0.015391",
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.56301, bump 0.54071",
    "vega-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: vega 24.14853, bump 12.92077",
    "implied-round-trip": "S=50.02, K=43.48, r=0.04716, q=0.0679, vol=0.3518, T=0.04529: recovered vol None",
    "monte-carlo": "S=86.69, K=119.6, r=0.1386, q=0.03725, vol=0.266, T=0.281: formula -0.2269, simulation 0.0917 ± 0.0049"
   }
  },
  {
   "id": "d1-half-sign",
   "title": "Wrong sign on the convexity term in d1",
   "why": "An easy slip when copying the formula.",
   "before": "(rate - div + 0.5 * vol * vol)",
   "after": "(rate - div - 0.5 * vol * vol)",
   "failures": {
    "textbook": "call(100, 100, 5%, 0, 20%, 1y) gave 10.2969, expected 10.4506",
    "bounds": "S=140.5, K=55.49, r=0.1361, q=0.03011, vol=0.3688, T=1.457: call=88.89650 outside [88.95242, 134.45816]",
    "strike-shape": "S=221.5, K=27.54, r=0.07764, q=0.006492, vol=1.132, T=0.2392: butterfly=-0.000088",
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.41818, bump 0.55169",
    "vega-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: vega 23.94732, bump 19.64383",
    "implied-round-trip": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: recovered vol None",
    "monte-carlo": "S=29.29, K=253.2, r=0.104, q=0.00335, vol=1.273, T=2.239: formula 0.7050, simulation 7.8627 ± 0.5079"
   }
  },
  {
   "id": "d1-no-carry",
   "title": "Dividend yield left out of d1",
   "why": "Harmless whenever the test uses a zero dividend.",
   "before": "(rate - div + 0.5 * vol * vol)",
   "after": "(rate + 0.5 * vol * vol)",
   "failures": {
    "bounds": "S=197.8, K=317.1, r=0.02976, q=0.04572, vol=0.1016, T=0.6442: call=-0.00000 outside [0.00000, 192.07009]",
    "strike-shape": "S=197.8, K=317.1, r=0.02976, q=0.04572, vol=0.1016, T=0.6442: call rises with strike",
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.56393, bump 0.56301",
    "vega-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: vega 24.13941, bump 24.14860",
    "implied-round-trip": "S=65.12, K=87.03, r=0.09507, q=0.07918, vol=0.08984, T=1.256: recovered vol None"
   }
  },
  {
   "id": "log-inverted",
   "title": "Log-moneyness inverted",
   "why": "Harmless at the money, where the log is zero.",
   "before": "log(spot / strike)",
   "after": "log(strike / spot)",
   "failures": {
    "bounds": "S=28.62, K=52.34, r=0.101, q=0.07369, vol=0.2135, T=0.1097: call=-23.37180 outside [0.00000, 28.38978]",
    "strike-shape": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: butterfly=-0.141966",
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.65661, bump 0.75070",
    "gamma-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: gamma 0.008772, bump of delta -0.008772",
    "vega-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: vega 22.53534, bump 24.87233",
    "implied-round-trip": "S=43.35, K=40.7, r=0.1215, q=0.02439, vol=0.1014, T=1.749: recovered vol None",
    "monte-carlo": "S=86.69, K=119.6, r=0.1386, q=0.03725, vol=0.266, T=0.281: formula -28.8342, simulation 0.0917 ± 0.0049",
    "limits": "near expiry: call=0.00000, expected payoff 10.00000"
   }
  },
  {
   "id": "simple-discount",
   "title": "Simple discounting on the strike",
   "why": "Close to the right number for small rates and short maturities.",
   "before": "strike * exp(-rate * time)",
   "after": "strike / (1.0 + rate * time)",
   "failures": {
    "textbook": "call(100, 100, 5%, 0, 20%, 1y) gave 10.3861, expected 10.4506",
    "parity": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: C-P=-1.03994, expected -0.96179",
    "bounds": "S=140.5, K=55.49, r=0.1361, q=0.03011, vol=0.3688, T=1.457: call=88.24835 outside [88.95242, 134.45816]",
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.56301, bump 0.56228",
    "vega-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: vega 24.14853, bump 24.15568",
    "limits": "vol=0.0, T=1.0: call=12.30558, expected 12.40922"
   }
  },
  {
   "id": "no-dividend-carry",
   "title": "Spot not carried at the dividend yield",
   "why": "Harmless whenever the test uses a zero dividend.",
   "before": "return spot * exp(-div * time), strike",
   "after": "return spot, strike",
   "failures": {
    "parity": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: C-P=-0.86354, expected -0.96179",
    "bounds": "S=438.6, K=29.55, r=0.04597, q=0.07411, vol=0.1842, T=1.826: call=411.45105 outside [355.92286, 383.09586]",
    "strike-shape": "S=271.8, K=106.7, r=0.03019, q=0.04308, vol=0.1196, T=1.922: butterfly=-0.000000",
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.56301, bump 0.56442",
    "vega-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: vega 24.14853, bump 24.16019",
    "implied-round-trip": "S=349.7, K=256.7, r=0.1146, q=0.06859, vol=0.1297, T=0.2849: recovered vol None",
    "monte-carlo": "S=86.69, K=119.6, r=0.1386, q=0.03725, vol=0.266, T=0.281: formula 0.1149, simulation 0.0917 ± 0.0049",
    "limits": "vol=0.0, T=1.0: call=14.38935, expected 12.40922"
   }
  },
  {
   "id": "call-d1-for-d2",
   "title": "Call uses N(d1) on both legs",
   "why": "A copy and paste error in one term.",
   "before": "return carried * N(d1) - discounted * N(d2)",
   "after": "return carried * N(d1) - discounted * N(d1)",
   "failures": {
    "textbook": "call(100, 100, 5%, 0, 20%, 1y) gave 3.1059, expected 10.4506",
    "parity": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: C-P=-17.69335, expected -0.96179",
    "bounds": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: call=-0.54196 outside [0.00000, 114.46600]",
    "strike-shape": "S=140.5, K=55.49, r=0.1361, q=0.03011, vol=0.3688, T=1.457: butterfly=-0.003693",
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.56301, bump 0.55396",
    "vega-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: vega 24.14853, bump -0.11416",
    "implied-round-trip": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: recovered vol None",
    "monte-carlo": "S=86.69, K=119.6, r=0.1386, q=0.03725, vol=0.266, T=0.281: formula -0.6474, simulation 0.0917 ± 0.0049"
   }
  },
  {
   "id": "put-sign",
   "title": "Put uses N(d2) instead of N(-d2)",
   "why": "A dropped minus sign.",
   "before": "return discounted * N(-d2) - carried * N(-d1)",
   "after": "return discounted * N(d2) - carried * N(-d1)",
   "failures": {
    "parity": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: C-P=17.84329, expected -0.96179"
   }
  },
  {
   "id": "delta-no-carry",
   "title": "Delta ignores the dividend yield",
   "why": "The price is right, only the Greek is wrong.",
   "before": "return exp(-div * time) * N(d1)",
   "after": "return N(d1)",
   "failures": {
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.56349, bump 0.56301",
    "gamma-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: gamma 0.009399, bump of delta 0.009407"
   }
  },
  {
   "id": "delta-d2",
   "title": "Delta uses d2",
   "why": "A common confusion, since N(d2) is the exercise probability.",
   "before": "d1, _ = d1_d2(spot, strike, rate, div, vol, time)\n    return exp(-div * time) * N(d1)",
   "after": "_, d1 = d1_d2(spot, strike, rate, div, vol, time)\n    return exp(-div * time) * N(d1)",
   "failures": {
    "delta-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: delta 0.41818, bump 0.56301",
    "gamma-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: gamma 0.009399, bump of delta 0.009321"
   }
  },
  {
   "id": "gamma-no-vol",
   "title": "Gamma missing the volatility term",
   "why": "The price is right, only the Greek is wrong.",
   "before": "n(d1) / (spot * vol * sqrt(time))",
   "after": "n(d1) / (spot * sqrt(time))",
   "failures": {
    "gamma-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: gamma 0.006413, bump of delta 0.009399"
   }
  },
  {
   "id": "gamma-time",
   "title": "Gamma uses time instead of its square root",
   "why": "Identical when time is exactly one year.",
   "before": "n(d1) / (spot * vol * sqrt(time))",
   "after": "n(d1) / (spot * vol * time)",
   "failures": {
    "gamma-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: gamma 0.017549, bump of delta 0.009399"
   }
  },
  {
   "id": "vega-no-root",
   "title": "Vega missing the square root of time",
   "why": "Identical when time is exactly one year.",
   "before": "n(d1) * sqrt(time)",
   "after": "n(d1)",
   "failures": {
    "vega-bump": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: vega 45.08572, bump 24.14853"
   }
  },
  {
   "id": "degenerate-undiscounted",
   "title": "Zero-volatility call ignores discounting",
   "why": "Only reached at the edge of the input space.",
   "before": "return max(carried - discounted, 0.0)",
   "after": "return max(spot - strike, 0.0)",
   "failures": {
    "limits": "vol=0.0, T=1.0: call=10.00000, expected 12.40922"
   }
  },
  {
   "id": "degenerate-guard-removed",
   "title": "Zero-volatility guard removed",
   "why": "Only reached at the edge of the input space.",
   "before": "return vol * sqrt(time) < 1e-12",
   "after": "return False",
   "failures": {
    "limits": "crashed: ZeroDivisionError: float division by zero"
   }
  },
  {
   "id": "implied-too-few-steps",
   "title": "Implied volatility search stops early",
   "why": "The answer looks plausible and is slightly off.",
   "before": "for _ in range(200):",
   "after": "for _ in range(6):",
   "failures": {
    "implied-round-trip": "S=114.6, K=119.8, r=0.1298, q=0.00299, vol=0.6823, T=0.2869: recovered vol 0.6640633671875"
   }
  }
 ],
 "survivors": [],
 "smallest_suite": [
  "delta-bump",
  "limits",
  "gamma-bump",
  "vega-bump",
  "parity",
  "implied-round-trip"
 ],
 "total_mutants": 16
}