Equity Option Pricing — Black-Scholes-Merton Family Price equity options and warrants for VA hedge effectiveness, derivative valuation, and Greeks-based risk reporting.
Family-level model covering Black-Scholes-Merton closed-form pricing of EUROPEAN-exercise equity options (COND-004 / INV-008: scope constrained to European; the production FinView L1 engine has no early-exercise path). Outputs price plus Greeks (delta, gamma, vega, rho, theta). Primary consumer is the VA hedge program (M-110). Two implementations: production (FinView L1, European only) and the independent challenger (Gold Copy L1, which additionally supports American exercise via binomial/LSMC but is used only for effective-challenge of the European pricing, not for production).
Inputs, processing, outputs
Methodology
M-131 prices vanilla equity options under the Black-Scholes-Merton (BSM)
closed-form analytic. The engine is FinView-resident:
finlib.derivatives.equity_option_pricer.EquityOptionPricer, bound to the
registry id finview.L1.equity_option_pricer via the slim @engine decorator
(Decision 023 binding-only governance). It is an L1 instrument pricer; at the
portfolio level it is dispatched by finview.L2.derivative_analytics
(DerivativeAnalytics._build_pricer, instrument_subtype == 'equity_option'),
which aggregates fair value and Greeks by risk category.
For spot S, strike K, time-to-expiry T (years), risk-free rate r,
continuous dividend yield q, and volatility σ, the engine forms
d1 = [ ln(S/K) + (r − q + ½σ²)·T ] / (σ·√T)
d2 = d1 − σ·√T
and prices the two flavours as
call = S·e^(−qT)·N(d1) − K·e^(−rT)·N(d2)
put = K·e^(−rT)·N(−d2) − S·e^(−qT)·N(−d1)
where N(·) is the standard-normal CDF (scipy.stats.norm.cdf). This is the
exact code in _d1_d2() / _bs_price() — there is no lattice, no Monte Carlo,
and no numerical integration; the price is a direct evaluation of the BSM
formula. The dividend term e^(−qT) makes this the Merton dividend-adjusted
variant, so equity-index options with a carry yield are handled correctly.
compute_fair_value() scales the per-unit premium to a position:
n_contracts = notional / spot (share-equivalent count) and
fair_value = sign · n_contracts · price_per_unit, where sign = +1 for a
long position and −1 for a short. The method returns fair_value,
price_per_unit, n_contracts, and the five Greeks.
Greeks are computed in closed form (not by bumping), each in a documented convention:
| Greek | Formula (call shown) | Convention in code |
|---|---|---|
| delta | e^(−qT)·N(d1) |
per unit of spot; put uses −e^(−qT)·N(−d1) |
| gamma | e^(−qT)·n(d1) / (S·σ·√T) |
second derivative wrt spot; sign-independent |
| vega | S·e^(−qT)·n(d1)·√T / 100 |
per 1% (1 vol-point) move, not per unit vol |
| theta | analytic θ / 365 | per calendar day (drift + carry terms) |
| rho | K·T·e^(−rT)·N(d2) / 100 |
per 1% rate move; put is negative |
n(·) is the normal PDF. Directional sign is applied to delta, vega, theta,
and rho (a short position flips them); gamma is reported unsigned. The scaling
constants (/100 for vega/rho, /365 for theta) are baked into the engine, so
consumers must read the Greeks in those units.
Key Assumptions and Their Justification
| Assumption | Value in canonical run | Source / derivation | Justification |
|---|---|---|---|
Volatility σ |
0.20 (caller-supplied) | book / market_data['implied_vol_surface'] at runtime |
BSM treats σ as a single constant flat vol. The engine consumes whatever the caller passes; DerivativeAnalytics._get_market_inputs tags equity-option vol as source: 'Book', source_type: 'book' — i.e. a position-level input, not a live surface lookup in the current build. |
Risk-free rate r |
0.04 | caller-supplied; DerivativeAnalytics default risk_free_rate=0.04 |
Flat continuously-compounded discount rate. There is no term-structure pull from the SOFR curve for options today — a single scalar r is used. |
Dividend yield q |
0.0 (ATM canonical) | caller-supplied; defaults to 0.0 |
Continuous-dividend Merton adjustment. The test_ql_validation index option uses q = 1.3% (S&P-style carry); zero is correct for a non-dividend single name / the canonical demonstration. |
| Exercise style | European | hard-coded (no early-exercise branch) | The closed form is exact only for European exercise. The engine has no American/early-exercise path (no lattice, no Bjerksund-Stensland approximation) — see Limitations. |
| Underlying dynamics | GBM, constant σ, r, q |
BSM model assumption | Lognormal spot with constant parameters. No skew/smile, no stochastic vol, no jumps — the standard BSM idealisation. |
| Position scaling | n_contracts = notional / spot |
compute_fair_value() |
Treats notional as a cash amount converted to share-equivalents at spot; long/short sign applied to fair value and first-order Greeks. |
Prose. The load-bearing assumption is flat constant volatility: BSM
collapses the entire implied-vol surface to one number, so the engine prices
on-strike but cannot reproduce a market smile across strikes without the caller
supplying a strike-specific σ. In the present build the vol and spot are
book/position inputs (source_type: 'book'), which is the honest state — there
is a vol_surface.py module in finlib (0% covered in the test run) but the
equity-option pricer does not consume it. The rate is a flat scalar rather than
a discount-curve pull, acceptable for short-dated single-name options but a
known simplification for longer tenors where the curve shape matters.
Output Snapshot
Deterministic single-option run of EquityOptionPricer
(finview.L1.equity_option_pricer), reproducible and requiring no live data
(BV-032 immune — every input is a literal). Run via
python scripts/model_snapshots.py M-131; the underlying analytics are asserted
by tests/test_equity_option_pricer.py (6 tests, all passing), cross-checked
against the independent Gold Copy challenger goldcopy.L1.option_pricer
(reconciliation in modelling/validation_evidence/M-131/v1.0.0/, max relative
diff 0.00e+00 within 2%), and supplementarily against QuantLib in
tests/test_ql_validation.py::TestOptionsValidation (skipped when un-installed).
Input: canonical ATM European call · S = 100 · K = 100 · T = 1.0y · r = 4% · σ = 20% · q = 0 · notional = 100 · long.
| output | value | meaning |
|---|---|---|
| price_per_unit (call premium) | 9.925054 | BSM call value per unit of spot |
| fair_value | 9.925054 | n_contracts = notional/spot = 1, so equals premium |
| delta | 0.617911 | e^(−qT)·N(d1) — ITM-leaning at ATM because of positive drift |
| gamma | 0.019069 | curvature of value in spot |
| vega (per 1% vol) | 0.381388 | premium change for a +1 vol-point move |
| theta (per day) | −0.016133 | daily time decay (long option loses value with time) |
| rho (per 1% rate) | 0.518661 | premium change for a +1% rate move |
| put-call parity residual | 0.00e+00 | (C − P) − (S·e^(−qT) − K·e^(−rT)) to machine precision |
The premium 9.925054 matches the textbook BSM closed form to six decimals
(independently recomputed from scipy.stats.norm); the well-known r = 5% ATM
value 10.4506 also reproduces, confirming the implementation. The delta exceeds
0.50 at-the-money because the positive risk-neutral drift (r − q + ½σ²) pushes
d1 > 0. Put-call parity holds to machine zero, the strongest internal
closed-form invariant the engine can satisfy. The identical table is produced by
both the FinView venv and the InsModel venv (the snapshot inserts
[redacted] on sys.path, mirroring the M-040 cross-repo pattern).
Captured 2026-06-04 · deterministic, no live data.
Limitations and Known Gaps
- European exercise only — scope constrained (COND-004 / INV-008,
remediated 2026-06-06). The production FinView engine has a single
closed-form path; there is no binomial/trinomial lattice and no analytic
American approximation (Barone-Adesi-Whaley / Bjerksund-Stensland). Any
American equity option (notably deep-ITM puts and dividend-paying calls where
early exercise has value) would be mispriced if routed here. Remediation:
rather than building an American path into the production engine, M-131's
scope has been formally constrained to European exercise — "American" has
been removed from the model description in both the registry entry and this
card's Description/Intended Use, and American equity options are declared
out of scope (see Description scope note). This closes INV-008 by the
scope-constraint route (charter §2.2; no fabrication). The Gold Copy
challenger
goldcopy.L1.option_pricerdoes implement American exercise (binomial / LSMC), but that capability is used only for effective-challenge, not for production pricing; if American production pricing is later required, it is a separate development-flow build (new engine capability), not a documentation fix. - Flat constant volatility — no smile/skew. A single scalar
σis used; the engine does not consume an implied-vol surface.finlib/vol_surface.pyexists but is not wired into the equity-option pricer (0% test coverage in the snapshot run). Off-ATM strikes priced with an ATM vol will diverge from market. - Flat scalar risk-free rate — no discount-curve pull.
ris a single number, not a tenor-interpolated rate off the bootstrapped SOFR/Treasury curve that the bond/IRS pricers use. Acceptable for short-dated options; a known simplification for longer maturities. - Vol and spot are book/position inputs, not live market. Per
DerivativeAnalytics._get_market_inputs, equity-option market inputs are taggedsource: 'Book'/source_type: 'book'. There is no live spot or live implied-vol feed in the present build, so portfolio valuation inherits the book's mark, not an independent market mark. - Constant continuous dividend yield only. Discrete cash dividends and
borrow/repo financing spreads are not modelled — only a single continuous
q. Single-name options with lumpy ex-dividend dates carry basis error. - No QuantLib at runtime, and not installed in either venv. QuantLib is a
supplementary test-only challenger (
@pytest.mark.quantlib, skipped if absent) and is not installed in the FinView or InsModel venvs at capture time, so the QuantLib cross-validation intest_ql_validation.pydoes not run in the default suite. This is no longer the operative cross-engine gap: the Gold Copy challengergoldcopy.L1.option_pricerreconciliation (6 cases, 0.00e+00 max relative price diff, within the registry's 2% tolerance — run 2026-06-06, COND-002) is the operative cross-engine challenge, with the put-call-parity invariant and closed-form recomputation as additional internal checks.
Status of formerly-tracked items (updated 2026-06-06, RAT-131 remediation). - INV-008 (American exercise) — remediated by scope constraint to European (COND-004): "American" removed from registry + card; American options out of scope. Re-opens only as a separate development-flow build if American production pricing is later required. - Cross-engine challenge — remediated by the documented finview-vs-goldcopy reconciliation (COND-002/003); the Gold Copy challenger is the challenger of record. QuantLib remains a supplementary skipped check. - Tier-2 validation + lifecycle — registry
last_validated_onstamped2026-06-06,documentation_packflags reconciled, validation-evidence pack assembled atmodelling/validation_evidence/M-131/v1.0.0/(COND-001). Independent 2L effective-challenge sign-off is pending 2L re-review. - Still tracked (genuine future work, not blockers): wire the DS-057 implied-vol surface into the pricer so off-ATM strikes price on a smile (Limitations #2/#4); define a packaged / versioned interface + CI drift gate for the FinView pricing library consumed cross-repo (INV-032, shared with M-040 / M-130–137).
Validation Packet
| Check | Mechanism | Status |
|---|---|---|
| Closed-form recomputation | Snapshot premium 9.925054 reproduced from an independent scipy.stats.norm BSM evaluation; matches to 6 dp. Textbook r = 5% ATM value 10.4506 also reproduced. |
Pass |
| Put-call parity invariant | Snapshot computes (C − P) − (S·e^(−qT) − K·e^(−rT)); residual = 0.00e+00. Asserted in test_put_call_parity (tol < 0.01). |
Pass (machine zero) |
| Delta bounds | test_delta_bounds: call delta ∈ [0, 1], put delta ∈ [−1, 0]. |
Pass |
| Vega monotonicity | test_vol_sensitivity: higher σ → higher premium. |
Pass |
| Positive premium / sign | test_positive_premium, test_short_direction: premiums > 0; short fair value < 0. |
Pass |
| Greeks present | test_greeks_present: delta, gamma, vega, theta returned. |
Pass |
| Full unit suite | tests/test_equity_option_pricer.py — 6 tests. |
6 passed |
| Cross-engine challenger (Gold Copy — challenger of record) | goldcopy.L1.option_pricer (ecosystem/gold_copy/instruments/option_pricer.py) is the registry-declared cross_check for finview.L1.equity_option_pricer (engine_registry, tolerance {price: 2%}). It is an independent first-principles BSM re-implementation that imports only gold_copy.* (no FinView/InsModel). Reconciliation run 2026-06-06 (COND-002): 6 option cases (ATM call/put, OTM call/put with dividend, long-dated high-vol, short-dated ITM) — max relative price diff 0.00e+00, all within the 2% tolerance; all five Greeks also reconcile to machine zero on the common convention scale. Reproducible via the validation-evidence pack. |
Pass (within 2%; machine zero) |
| Cross-engine challenger (QuantLib — supplementary) | tests/test_ql_validation.py::TestOptionsValidation prices the same options with ql.AnalyticEuropeanEngine (BlackScholesMertonProcess); tolerances OPTION_PRICE_TOL = 5%, GREEK_TOL = 5% (delta). Test-only dependency, marked @pytest.mark.quantlib, skipped — QuantLib not installed in either venv at capture. Supplementary only; the Gold Copy reconciliation above is the operative cross-engine challenge. |
Not run (skipped) |
Challenger of record (COND-003, corrected 2026-06-06). The challenger of
record is the Gold Copy engine goldcopy.L1.option_pricer, which the
engine_registry binds as M-131's cross_check (tolerance {price: 2%}). It is
an independent BSM re-implementation living in the gold_copy repo (not in
FinView), import-isolated from FinView/InsModel. A prior version of this card
stated "there is no gold_copy / Gold Copy challenger engine" for equity
options and named QuantLib as the challenger of record — that was incorrect and
understated the registry-declared independent challenger; it is corrected here.
The Gold Copy engine genuinely exists, and the documented finview-vs-goldcopy
reconciliation (6 cases, 0.00e+00 max relative diff, well within 2%) is now on
file as the operative cross-engine challenge — upgrading M-131 from
"internal-invariant validated" to "cross-engine validated." QuantLib's
AnalyticEuropeanEngine remains a supplementary test-only challenger (skipped
when un-installed); the put-call-parity invariant and independent closed-form
recomputation remain in force as additional internal checks.
References
- Black, F. & Scholes, M. (1973), "The Pricing of Options and Corporate
Liabilities," Journal of Political Economy 81(3) — the closed-form
d1/d2call/put formulae. - Merton, R. C. (1973), "Theory of Rational Option Pricing," Bell Journal
of Economics — the continuous-dividend
e^(−qT)adjustment implemented here. - Engine source:
[redacted](EquityOptionPricer,finview.L1.equity_option_pricer). - Portfolio dispatch:
[redacted](DerivativeAnalytics,finview.L2.derivative_analytics). - Unit tests:
[redacted](6 tests). - Cross-engine (QuantLib) validation:
[redacted]::TestOptionsValidation(@pytest.mark.quantlib). - Governance binding:
[redacted](slim@enginedecorator, Decision 023). - Snapshot driver pattern:
[redacted](snap_M_040is the FinView-resident cross-repo precedent).
Findings: - INV-008 (2L) — European-only closed form; American equity options would be mispriced if routed here. Remediated 2026-06-06 by scope constraint to European exercise (COND-004): American removed from registry + card scope, American options declared out of scope. See Limitation #1 and the Description scope note. Re-opens only as a separate development-flow build if American production pricing is required.
Change Log
Card change history. Code-side change history lives in git log of the component files.
- 2026-05-08 — stub created from registry data per Decision 023 Phase 5 / B-07.
- 2026-06-04 — Tier-2 hand-authoring of Methodology, Key Assumptions and Their Justification, Limitations and Known Gaps, Validation Packet, and References from the engine source (
finlib/derivatives/equity_option_pricer.py) +DerivativeAnalyticsdispatch. Stub marker advanced to`. Output Snapshot captured the same day (python scripts/model_snapshots.py M-131`, deterministic, no live data). - 2026-06-05 — card stamped in
modelling/model_doc_stamps.yaml(fingerprint3de198425154bf81, engine watermark4e65d861fdbc). - 2026-06-06 — documentation-accuracy pass against the engine source. Added missing Standards Coverage section (ASOP 56 / SR 26-2 / internal); added DS-057 (options IV surface, PL-07, planned) to Components to match the registry entry while preserving the "not yet wired" posture; cross-referenced 2L finding INV-008 (open) under Limitation #1 + References; consolidated the gated/ratification items (INV-008, DS-057 wiring, QuantLib challenger, Tier-2 sign-off + lifecycle, INV-032) into a transparency note. No model outputs, validation results, or back-test numbers were fabricated or changed.
- 2026-06-06 (RAT-131-v1.0.0 1L remediation, Decision 053 §2.2) — remediated COND-001..004 with real, reproducible evidence only:
- COND-003 — corrected the challenger of record: the prior "no
gold_copychallenger engine" note was wrong;goldcopy.L1.option_priceris the registry-declared independent challenger and is now named as such in the Validation Packet. - COND-002 — ran and documented the finview-vs-goldcopy cross-engine reconciliation (6 cases: ATM call/put, OTM ±dividend, long-dated high-vol, short-dated ITM) → max relative price diff 0.00e+00, all five Greeks reconcile to machine zero on the common convention scale, all within the registry's 2% tolerance. Assembled the validation-evidence pack
modelling/validation_evidence/M-131/v1.0.0/. - COND-004 / INV-008 — constrained M-131 scope to European exercise; removed "American" from the registry description and the card Description/Limitations; declared American options out of scope (closed by scope constraint, not a build).
- COND-001 — reconciled the registry
documentation_packflags (model_card/validation_evidence/change_log→ present), stampedlifecycle.last_validated_on: 2026-06-06. No model outputs invented; QuantLib remains honestly marked skipped. Ready for 2L re-review.
Open findings (2)
Independent 2nd-line review (INV-2026-06) — implemented capability vs registered scope. Each carries a recommended fix and is tracked in insightalm-mrm until closed.
Validation evidence + change logs missing across most of the inventory
Only M-001/M-020/M-050 carried full documentation packs before this pass. Most models record validation_evidence: missing and change_log: missing with peer_review: pending. Gold tests freeze behaviour but many assert only structural invariants (e.g. reserve>0), not correctness against external truth. The flagship T0-vs-10-K match is circular (BV-032).
Recommendation: For each Tier-1 model: produce a validation-evidence pack (back-test vs disclosed results once BV-032 re-calibration lands, sensitivity suite, challenger comparison), a change log, and a 2L ratification. Sequence behind BV-032 (firm-data) for anything needing 10-K reconciliation.
FinView-resident pricing/CECL engines reached cross-repo with no drift gate
M-040 (CECL) and M-130-137 (asset pricing) are FinView-resident, consumed by InsModel only via sys.path insertion. No packaged interface or drift gate — a finlib signature change silently breaks the InsModel snapshot harness / consumers.
Recommendation: Define a packaged interface (or a versioned contract) for the FinView pricing library and a CI drift gate, so cross-repo consumers fail loudly on signature change. Tie to D041 ownership.
Per-tier expectations
Per MRM Framework §10.2 + §10.3, this model's regulatory_frameworks tag list activates the following overlays:
| component | tier-2 expectation | status |
|---|---|---|
| Registry entry | required | present |
| Model card (§10.5 doc pack) | required | present |
| Validation evidence | required | present |
| Change log | required | present |
| Independent effective challenge (2L) | required | attested |
Ratified — RAT-131-v1.0.1
Latest ratification on file: RAT-131-v1.0.1. Authored by 2L (mrm-peer-reviewer) per Decision 028 charter §5 Pattern A.