Skip to content

Commit

Permalink
WIP Risk Measure Revamp
Browse files Browse the repository at this point in the history
  • Loading branch information
alecloudenback committed Apr 17, 2024
1 parent 017c749 commit 648ae12
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 56 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"

[compat]
Distributions = "0.24,0.25"
Distributions = "0.25"
FinanceCore = "^2"
FinanceModels = "^4"
ForwardDiff = "^0.10"
Expand Down
1 change: 1 addition & 0 deletions src/ActuaryUtilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using MuladdMacro
import FinanceModels
import StatsBase
using PrecompileTools
import Distributions

include("financial_math.jl")
include("risk_measures.jl")
Expand Down
177 changes: 132 additions & 45 deletions src/risk_measures.jl
Original file line number Diff line number Diff line change
@@ -1,49 +1,136 @@
"""
VaR(v::AbstractArray,p::Real;rev::Bool=false)
The `p`th quantile of the vector `v` is the Value at Risk. Assumes more positive values are higher risk measures, so a higher p will return a more positive number, but this can be reversed if `rev` is `true`.
Also can be called with `ValueAtRisk(...)`.
"""
function VaR(v, p; rev=false)
if rev
return StatsBase.quantile(v, 1 - p)
else
return StatsBase.quantile(v, p)
end
abstract type DistortionFunction end


struct Expectation <: DistortionFunction end
(g::Expectation)(x) = x

struct VaR{T} <: DistortionFunction
α::T
end
(g::VaR)(x) = x < (1 - g.α) ? 0 : 1

struct CTE{T} <: DistortionFunction
α::T
end
(g::CTE)(x) = x < (1 - g.α) ? x / (1 - g.α) : 1

struct WangTransform{T} <: DistortionFunction
α::T
end
function (g::WangTransform)(x)
Φ_inv(x) = Distributions.quantile(Distributions.Normal(), x)
Distributions.cdf(Distributions.Normal(), Φ_inv(x) + g.α)
end
"""
[`VaR`](@ref)
"""
ValueAtRisk = VaR

"""
CTE(v::AbstractArray,p::Real;rev::Bool=false)
The average of the values ≥ the `p`th percentile of the vector `v` is the Conditiona Tail Expectation. Assumes more positive values are higher risk measures, so a higher p will return a more positive number, but this can be reversed if `rev` is `true`.
May also be called with `ConditionalTailExpectation(...)`.
Also known as Tail Value at Risk (TVaR), or Tail Conditional Expectation (TCE)
"""
function CTE(v, p; rev=false)
# filter has the "or approximately equalt to quantile" because
# of floating point path might make the quantile slightly off from the right indexing
# e.g. if values should capture <= q, where q should be 10 but is calculated to be
# 9.99999...
if rev
q = StatsBase.quantile(v, 1 - p)
filter = (v .<= q) .| (v .≈ q)
else
q = StatsBase.quantile(v, p)
filter = (v .>= q) .| (v .≈ q)
end

return sum(v[filter]) / sum(filter)

struct DualPower{T} <: DistortionFunction
v::T
end
(g::DualPower)(x) = 1 - (1 - x)^g.v

struct ProportionalHazard{T} <: DistortionFunction
y::T
end
(g::ProportionalHazard)(x) = x^(1 / g.y)


function ρ(g::DistortionFunction, risk)

# integral from 0 to infinity of g(S(x))dx
# where S(x) is 1-cdf(risk,x)
F(x) = cdf_func(risk)(x)
S(x) = 1 - F(x)
H(x) = 1 - g(1 - x)
integral1, _ = quadgk(x -> 1 - H(F(x)), 0, Inf)
integral2, _ = quadgk(x -> H(F(x)), -Inf, 0)
return integral1 - integral2
end


cdf_func(S::AbstractArray{<:Real}) = StatsBase.ecdf(S)
cdf_func(S::Distributions.UnivariateDistribution) = x -> Distributions.cdf(S, x)

# """
# VaR(v::AbstractArray,p::Real;rev::Bool=false)

# The `p`th quantile of the vector `v` is the Value at Risk. Assumes more positive values are higher risk measures, so a higher p will return a more positive number, but this can be reversed if `rev` is `true`.

# Also can be called with `ValueAtRisk(...)`.
# """
# function VaR(v::T, p; sorted=false) where {T<:AbstractArray}
# if sorted
# _VaR_sorted(v, p)
# else
# _VaR_sorted(sort(v), p)
# end
# end

# # Core VaR assumes v is sorted
# function _VaR_sorted(v, p)
# i = 1
# n = length(v)
# q_prior = 0.0
# x_prior = first(v)
# for (i, x) in enumerate(v)
# q = i / n
# if q >= p
# # return weighted between two points
# return x * (p - q_prior) / (q - q_prior) + x_prior * (q - p) / (q - q_prior)

# end
# x_prior = x
# q_prior = q
# end

# return last(v)
# end


# """
# [`VaR`](@ref)
# """
# ValueAtRisk = VaR

# """
# CTE(v::AbstractArray,p::Real;rev::Bool=false)

# The average of the values ≥ the `p`th percentile of the vector `v` is the Conditiona Tail Expectation. Assumes more positive values are higher risk measures, so a higher p will return a more positive number, but this can be reversed if `rev` is `true`.

# May also be called with `ConditionalTailExpectation(...)`.

# Also known as Tail Value at Risk (TVaR), or Tail Conditional Expectation (TCE)
# """
# function CTE(v::T, p; sorted=false) where {T<:AbstractArray}
# if sorted
# _CTE_sorted(v, p)
# else
# _CTE_sorted(sort(v), p)
# end
# end

# # Core CTE assumes v is sorted
# function _CTE_sorted(v, p)
# i = 1
# n = length(v)
# q_prior = 0.0
# x_prior = first(v)
# sub_total = zero(eltype(v))
# in_range = false
# for (i, x) in enumerate(v)
# q = i / n
# if in_range || q >= p
# # return weighted between two points
# # return x * (p - q_prior) / (q - q_prior) + x_prior * (q - p) / (q - q_prior)
# in_range = true

# end
# x_prior = x
# q_prior = q
# end

# return last(v)
# end

"""
[`CTE`](@ref)
"""
ConditionalTailExpectation = CTE
# """
# [`CTE`](@ref)
# """
# ConditionalTailExpectation = CTE
34 changes: 24 additions & 10 deletions test/risk_measures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@
sample = [0:100...]

@testset "VaR" begin
@test VaR(sample,.9) 90
@test VaR(sample,.9,rev=true) 10
@test VaR(sample,.1) VaR(sample,1-.9)
@test VaR(sample,.1) == ValueAtRisk(sample,.1)
@test VaR(sample, 0.9) 90
@test VaR(sample, 0.9, rev=true) 10
@test VaR(sample, 0.1) VaR(sample, 1 - 0.9)
@test VaR(sample, 0.1) == ValueAtRisk(sample, 0.1)
end

@testset "CTE" begin
@test CTE(sample,.9) sum(90:100) / length(90:100)
@test CTE(sample,.1) sum(10:100) / length(10:100)
@test CTE(sample,.15) >= CTE(sample,.1) # monotonicity
@test CTE(sample,.9,rev=true) sum(0:10) / length(0:10)
@test CTE(sample,.9) == ConditionalTailExpectation(sample,.9)
@test CTE(sample, 0.9) sum(90:100) / length(90:100)
@test CTE(sample, 0.1) sum(10:100) / length(10:100)
@test CTE(sample, 0.15) >= CTE(sample, 0.1) # monotonicity
@test CTE(sample, 0.9, rev=true) sum(0:10) / length(0:10)
@test CTE(sample, 0.9) == ConditionalTailExpectation(sample, 0.9)
end


@testset "duplicated values" begin
sample = zeros(100)
sample[end] = 100

@test CTE(sample, 0) 100 / 100
@test CTE(sample, 0.5) 100 / 50

@test VaR(sample, 0) 0
@test VaR(sample, 0.5) 0
@test VaR(sample, 0.99) 0

end

end
Expand Down

0 comments on commit 648ae12

Please sign in to comment.