Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Heston model #28

Merged
merged 3 commits into from
Aug 7, 2024
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
6 changes: 3 additions & 3 deletions Manifest.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.10.1"
julia_version = "1.10.4"
manifest_format = "2.0"
project_hash = "1907bfa46a4a9d8677f7cb8f159f242fb390271d"
project_hash = "e7532eff98d0469a2cec85b664122030cc515a13"

[[deps.AliasTables]]
deps = ["Random"]
Expand Down Expand Up @@ -39,7 +39,7 @@ weakdeps = ["Dates", "LinearAlgebra"]
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.1.0+0"
version = "1.1.1+0"

[[deps.DataAPI]]
git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe"
Expand Down
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ authors = "Alexandre Brilhante"
version = "0.1.0"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4 changes: 3 additions & 1 deletion src/FinancialDerivatives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export BlackKarasinski,
LongstaffSchwartz,
RendlemanBartter,
Tian,
Vasicek
Vasicek,
HestonModel

export evaluate

Expand All @@ -45,5 +46,6 @@ include("models/longstaff_schwartz.jl")
include("models/rendleman_bartter.jl")
include("models/tian.jl")
include("models/vasicek.jl")
include("models/heston.jl")

end # module
69 changes: 69 additions & 0 deletions src/models/heston.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using QuadGK: quadgk

"""
[Heston model](https://en.wikipedia.org/wiki/Heston_model).
"""
@kwdef struct HestonModel{VT<:Real,VVT<:Real,KT<:Real,RT<:Real}
mvanzulli marked this conversation as resolved.
Show resolved Hide resolved
"Initial stock variance"
v::VT
"Long term variance mean"
v̄::VVT
"Mean reversion term of stock variance `v`"
κ::KT
"Correlation between stock price `s` and it's variance `v`"
ρ::RT
end

""""
Evaluate option `O` using the [`HestonModel`](@ref) model.
"""
function evaluate(O::EuropeanOption, m::HestonModel; N=100)
k, t, s, q, r, σ = O.k, O.t, O.s, O.q, O.r, O.σ
κ, v̄ = m.κ, m.v̄

2 * κ * v̄ ≥ σ^2 ||
throw(ArgumentError("The Feller condition is not satisfied: 2κθ ≥ σ^2"))

function integrand1(ω)
return real(exp(-im * ω * log(k)) * φ(O, m, ω - im) /
(im * ω * φ(O, m, -im)))
end

function integrand2(ω)
return real(exp(-im * ω * log(k)) * φ(O, m, ω) /
(im * ω))
end

π1, _ = quadgk(integrand1, 0, N)
π2, _ = quadgk(integrand2, 0, N)

π1 = π1 / π + 0.5
π2 = π2 / π + 0.5

C = s * exp(-q * t) * π1 - k * exp(-r * t) * π2
if iscall(O)
C

Check warning on line 45 in src/models/heston.jl

View check run for this annotation

Codecov / codecov/patch

src/models/heston.jl#L45

Added line #L45 was not covered by tests
elseif isput(O) # Calculate the put option value using put-call parity
P = C + k * exp(-r * t) - s * exp(-q * t)
end
end

"""
Characteristic function Heston model for an European call option from [A Closed-Form Solution for Options with Stochastic Volatility with Applications to Bond and Currency Options, L. Heston](https://www.jstor.org/stable/2962057)
"""
function φ(O::EuropeanOption, m::HestonModel, ω::Number)
(; v, v̄, κ, ρ) = m
s, r, q, σ, t = O.s, O.r, O.q, O.σ, O.t

α = -ω^2 / 2 - im * ω / 2
β = κ - ρ * σ * im * ω
γ = σ^2 / 2
h = sqrt(β^2 - 4 * α * γ)
r₊ = (β + h) / σ^2
r₋ = (β - h) / σ^2
g = r₋ / r₊
C = κ * (r₋ * t - (2 / σ^2) * log((1 - g * exp(-h * t)) / (1 - g)))
D = r₋ * (1 - exp(-h * t)) / (1 - g * exp(-h * t))

return exp(C * v̄ + D * v + im * ω * log(s * exp((r - q) * t)))
end
33 changes: 31 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FinancialDerivatives
using Test
using Dates
using FinancialDerivatives

eu_put = EuropeanOption(; s=100.0, k=90.0, r=0.05, q=0.01, σ=0.3, t=180 / 365, call=false)
eu_call = EuropeanOption(; s=eu_put.s, k=eu_put.k, r=eu_put.r, q=eu_put.q, σ=eu_put.σ,
Expand Down Expand Up @@ -29,7 +30,7 @@ end

@testset "Longstaff-Schwartz" begin
@test evaluate(am_put, LongstaffSchwartz()) ≈ 3.22 atol = 0.4
@test evaluate(am_call, LongstaffSchwartz()) ≈ 15.42 atol = 0.4
@test evaluate(am_call, LongstaffSchwartz()) ≈ 15.42 atol = 1.0
end

@testset "Tian" begin
Expand Down Expand Up @@ -63,3 +64,31 @@ end
@testset "Vasicek" begin
@test evaluate(ird, Vasicek(), 2) ≈ [0.2, 0.2, 0.2, 0.2] atol = 0.025
end

@testset "Heston" begin
s = 56371 # Initial stock price
r = 0.05078 # Risk-free rate
σ = 0.86 # Volatility
q = 0.005 # Dividend yield (staking yield)
k = 58000 # Strike price
v = σ^2 # Initial variance
v̄ = σ^2 # Long term variance mean
κ = 2 # Mean reversion rate
σ_v = 0.86 # Volatility of volatility
ρ = -0.7 # Correlation between price and volatility

t0 = Date(2024, 5, 11)
t1 = t0 + Month(3)
τ = t1 - t0
year_days = Dates.value(lastdayofyear(Date(2024))) -
Dates.value(firstdayofyear(Date(2024)))
t = Dates.value(τ) / year_days

m = HestonModel(v, v̄, κ, ρ)
eu_put = EuropeanOption(; s, k, r, q, σ, t, call=false)
eu_call = EuropeanOption(; s, k, r, q, σ, t, call=true)

@test evaluate(eu_call, m) ≈ 8982 atol = 1
@test evaluate(eu_call, m) + eu_call.k * exp(-eu_call.r * eu_call.t) ==
evaluate(eu_put, m) + eu_put.s * exp(-eu_put.q * eu_put.t)
end
Loading