Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 48 additions & 36 deletions examples/08_barriers.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"id": "458cebb9",
"metadata": {},
"outputs": [],
Expand All @@ -41,7 +41,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "62883135",
"metadata": {},
"outputs": [],
Expand Down Expand Up @@ -75,7 +75,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"id": "fb3abc32",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -118,7 +118,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"id": "82083afd",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -159,7 +159,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 5,
"id": "b9f9d3f0",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -204,7 +204,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 6,
"id": "969eccf5",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -257,7 +257,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 7,
"id": "924344e1",
"metadata": {},
"outputs": [
Expand All @@ -269,7 +269,7 @@
"------------------------------------------\n",
"bsm 11.0529 11.4489\n",
"binomial 11.0558 11.3924\n",
"pde_fd 11.0529 11.4491\n"
"pde_fd 11.0529 11.4502\n"
]
}
],
Expand Down Expand Up @@ -302,15 +302,15 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 8,
"id": "845efb07",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"DOC with 5 custom monitoring dates: 11.8989\n"
"DOC with 5 custom monitoring dates: 11.8981\n"
]
}
],
Expand Down Expand Up @@ -345,7 +345,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 9,
"id": "e859736d",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -397,22 +397,22 @@
"name": "stdout",
"output_type": "stream",
"text": [
"UOP European (Binomial): 15.1171\n",
"UOP European (PDE_FD): 15.1147\n",
"UOP American (Binomial): 16.6296\n",
"UOP American (PDE_FD): 16.6280\n",
"Early-exercise premium (Binomial): +1.5125\n",
"Early-exercise premium (PDE_FD): +1.5133\n"
"UOP European (Binomial): 7.8446\n",
"UOP European (PDE_FD): 7.8432\n",
"UOP American (Binomial): 8.5558\n",
"UOP American (PDE_FD): 8.5541\n",
"Early-exercise premium (Binomial): +0.7112\n",
"Early-exercise premium (PDE_FD): +0.7109\n"
]
}
],
"source": [
"uop_spec = dp.BarrierSpec(\n",
" option_type=dp.OptionType.PUT,\n",
" exercise_type=dp.ExerciseType.EUROPEAN,\n",
" strike=115.0, # deep-ITM put (spot=100)\n",
" strike=105.0, # ITM put (spot=100)\n",
" maturity=maturity,\n",
" barrier=130.0, # up-barrier above spot\n",
" barrier=115.0, # up-barrier above spot (and strike)\n",
" direction=dp.BarrierDirection.UP,\n",
" action=dp.BarrierAction.OUT,\n",
" monitoring=dp.BarrierMonitoring.CONTINUOUS,\n",
Expand Down Expand Up @@ -450,7 +450,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 11,
"id": "1825e67a",
"metadata": {},
"outputs": [],
Expand All @@ -465,7 +465,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 12,
"id": "a0b8764f",
"metadata": {},
"outputs": [
Expand All @@ -475,11 +475,11 @@
"text": [
"Greek BSM Binomial PDE_FD\n",
"------------------------------------------\n",
"delta -0.6501 -0.6501 -0.6501\n",
"gamma 0.0119 0.0119 0.0119\n",
"vega 0.2715 nan 0.2715\n",
"theta 0.0008 0.0008 0.0008\n",
"rho -0.7372 -0.7373 -0.7372\n"
"delta -0.5886 -0.5886 -0.5886\n",
"gamma 0.0100 0.0100 0.0100\n",
"vega 0.1688 nan 0.1688\n",
"theta 0.0006 0.0006 0.0006\n",
"rho -0.4655 -0.4656 -0.4655\n"
]
}
],
Expand All @@ -503,7 +503,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 13,
"id": "8f00cba5",
"metadata": {},
"outputs": [
Expand All @@ -513,11 +513,11 @@
"text": [
"Greek Binomial PDE_FD\n",
"------------------------------------------\n",
"delta -0.7473 -0.7475\n",
"gamma 0.0175 0.0175\n",
"vega nan 0.2098\n",
"theta -0.0025 -0.0025\n",
"rho -0.3454 -0.3450\n"
"delta -0.6521 -0.6522\n",
"gamma 0.0133 0.0133\n",
"vega nan 0.1520\n",
"theta -0.0013 -0.0013\n",
"rho -0.2881 -0.2878\n"
]
}
],
Expand Down Expand Up @@ -559,7 +559,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 14,
"id": "14721416",
"metadata": {},
"outputs": [
Expand All @@ -568,7 +568,7 @@
"output_type": "stream",
"text": [
"DOC with discrete divs (PDE_FD): 9.5619\n",
"DOC without divs (PDE_FD): 15.1147\n"
"DOC without divs (PDE_FD): 7.8432\n"
]
}
],
Expand Down Expand Up @@ -606,7 +606,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 15,
"id": "e85a339a",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -658,7 +658,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 16,
"id": "93bf3338",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -713,6 +713,18 @@
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
Expand Down
37 changes: 5 additions & 32 deletions src/derivatives_pricing/valuation/barrier_analytical.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,44 +477,17 @@ def theta(self) -> float:
central-difference bump-and-revalue around the same closed-form
evaluator (routed through :attr:`valuation_ctx` so repeated calls
hit the OV-level cache). The identity holds in the continuation
region; for inception-triggered KOs we short-circuit to a
closed-form θ because the contract is no longer PDE-governed
(it's just paid cash, or a deterministic discounted payment).
For inception-triggered KIs we delegate to the vanilla equivalent.
region only. Inception-triggered barriers are short-circuited
upstream in :meth:`OptionValuation.theta` (the PDE identity gives a
wrong answer for AT_HIT rebate KOs — paid cash doesn't accrue but
the identity returns ``r·R/365``), so this method may assume the
barrier is alive at pricing date.

Returned per **calendar day**
"""
ctx = self.valuation_ctx
underlying = self.underlying

# ── Inception-triggered short-circuit ─────────────────────────
# KO triggered: the contract has collapsed to a deterministic
# cashflow and is no longer PDE-governed. The identity then gives
# the wrong answer — e.g. r·V instead of 0 for an AT_HIT rebate
# — so we return the closed-form θ directly.
# KI triggered: the contract is the underlying vanilla, which DOES
# satisfy the BS PDE; the identity would still hold here (with the
# vanilla's δ and γ that OV's NUMERICAL short-circuit already
# provides). We delegate to ``vanilla.theta()`` anyway as a
# precision upgrade — it returns the analytical θ rather than the
# identity evaluated with bumped greeks.
if ctx._barrier_triggered_at_inception():
spec = self.spec
if spec.action is BarrierAction.IN:
return float(ctx._vanilla_equivalent_valuation().theta())
# KO triggered.
if spec.rebate <= 0.0 or spec.rebate_timing is RebateTiming.AT_HIT:
# No rebate or rebate paid immediately → pv has no time
# evolution → θ = 0.
return 0.0
# AT_EXPIRY rebate: pv = R · df_r(T), so dpv/dt = +r · pv;
# per-day θ = r · pv / 365.
T = ctx._maturity_year_fraction()
df_r = float(ctx.discount_curve.df(T))
pv = float(spec.rebate) * df_r
r = -np.log(df_r) / T
return float(r * pv / 365.0)

S = float(underlying.initial_value)
sigma = float(underlying.volatility)
sigma2 = sigma**2
Expand Down
Loading
Loading