Skip to content

Commit 7711736

Browse files
committed
Better return risk measures
1 parent 5cdf35e commit 7711736

File tree

7 files changed

+151
-100
lines changed

7 files changed

+151
-100
lines changed

src/19_RiskMeasures/01_Base_RiskMeasures.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ abstract type AbstractBaseRiskMeasure <: AbstractEstimator end
1818
function needs_previous_weights(::AbstractBaseRiskMeasure)
1919
return false
2020
end
21-
function smaller_is_better(::AbstractBaseRiskMeasure)
22-
return true
21+
function bigger_is_better(::AbstractBaseRiskMeasure)
22+
return false
2323
end
2424
const VecBaseRM = AbstractVector{<:AbstractBaseRiskMeasure}
2525
function needs_previous_weights(r::VecBaseRM)

src/19_RiskMeasures/23_NonOptimisationRiskMeasures.jl

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ end
1010
function MeanReturn(; w::Option{<:StatsBase.AbstractWeights} = nothing)
1111
return MeanReturn(w)
1212
end
13-
function smaller_is_better(::Union{<:MeanReturn,
14-
<:RiskRatioRiskMeasure{<:MeanReturn, <:Any}})
15-
return false
16-
end
1713
function (r::MeanReturn)(x::VecNum)
1814
return isnothing(r.w) ? Statistics.mean(x) : Statistics.mean(x, r.w)
1915
end
@@ -24,6 +20,27 @@ end
2420
function risk_measure_view(r::MeanReturn, ::Any, args...)
2521
return r
2622
end
23+
struct MeanReturnRiskRatio{T1, T2, T3} <: NonOptimisationRiskMeasure
24+
rt::T1
25+
rk::T2
26+
rf::T3
27+
function MeanReturnRiskRatio(rt::MeanReturn, rk::AbstractBaseRiskMeasure, rf::Number)
28+
return new{typeof(rt), typeof(rk), typeof(rf)}(rt, rk, rf)
29+
end
30+
end
31+
function MeanReturnRiskRatio(; rt::MeanReturn = MeanReturn(),
32+
rk::AbstractBaseRiskMeasure = ConditionalValueatRisk(),
33+
rf::Number = 0.0)
34+
return MeanReturnRiskRatio(rt, rk, rf)
35+
end
36+
function factory(r::MeanReturnRiskRatio, pr::AbstractPriorResult, args...; kwargs...)
37+
rt = nothing_scalar_array_selector(r.rt, pr)
38+
rk = factory(r.rk, pr, args...; kwargs...)
39+
return MeanReturnRiskRatio(; rt = rt, rk = rk, rf = r.rf)
40+
end
41+
function factory(r::MeanReturnRiskRatio, w::VecNum)
42+
return MeanReturnRiskRatio(; rt = r.rt, rk = factory(r.rk, w), rf = r.rf)
43+
end
2744
struct ThirdCentralMoment{T1, T2} <: NonOptimisationRiskMeasure
2845
w::T1
2946
mu::T2
@@ -116,4 +133,4 @@ function (r::Skewness)(w::VecNum, X::MatNum, fees::Option{<:Fees} = nothing)
116133
return res / sigma^3
117134
end
118135

119-
export MeanReturn, ThirdCentralMoment, Skewness
136+
export MeanReturn, ThirdCentralMoment, Skewness, MeanReturnRiskRatio

src/19_RiskMeasures/25_ExpectedRisk.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ function expected_risk(r::RkRatioRM, w::VecNum, pr::Pr_RR, fees::Option{<:Fees}
5858
return expected_risk(r.r1, w, pr.X, fees; kwargs...) /
5959
expected_risk(r.r2, w, pr.X, fees; kwargs...)
6060
end
61+
function expected_risk(r::MeanReturnRiskRatio, w::VecNum, X::MatNum,
62+
fees::Option{<:Fees} = nothing; kwargs...)
63+
return (expected_risk(r.rt, w, X, fees; kwargs...) - r.rf) /
64+
expected_risk(r.rk, w, X, fees; kwargs...)
65+
end
66+
function expected_risk(r::MeanReturnRiskRatio, w::VecNum, pr::Pr_RR,
67+
fees::Option{<:Fees} = nothing; kwargs...)
68+
return (expected_risk(r.rt, w, pr.X, fees; kwargs...) - r.rf) /
69+
expected_risk(r.rk, w, pr.X, fees; kwargs...)
70+
end
6171
function expected_risk(r::AbstractBaseRiskMeasure, w::VecVecNum, args...; kwargs...)
6272
return [expected_risk(r, wi, args...; kwargs...) for wi in w]
6373
end

src/20_Optimisation/02_CrossValidation/01_Base_CrossValidation.jl

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ function PredictionResult(; res::NonFiniteAllocationOptimisationResult,
9595
return PredictionResult(res, rd)
9696
end
9797
struct SingletonVector{T} <: AbstractVector{T} end
98+
function SingletonVector()
99+
return SingletonVector{Int}()
100+
end
98101
Base.length(::SingletonVector) = 1
99102
Base.getindex(::SingletonVector, args...) = 1
100103
Base.:*(M::Matrix, ::SingletonVector) = dropdims(M; dims = 2)
@@ -108,8 +111,7 @@ function expected_risk(pred::PredictionResult{<:Any,
108111
<:PredictionReturnsResult{<:Any, <:VecVecNum}},
109112
r::AbstractBaseRiskMeasure; kwargs...)
110113
X = pred.rd.X
111-
return [expected_risk(r, SingletonVector{Int}(), reshape(Xi, :, 1); kwargs...)
112-
for Xi in X]
114+
return [expected_risk(r, SingletonVector(), reshape(Xi, :, 1); kwargs...) for Xi in X]
113115
end
114116
struct MultiPeriodPredictionResult{T1} <: AbstractResult
115117
pred::T1
@@ -184,6 +186,17 @@ function expected_risk(ppred::PopulationPredictionResult, r::AbstractBaseRiskMea
184186
kwargs...)
185187
return [expected_risk(pred, r; kwargs...) for pred in ppred.pred]
186188
end
189+
function sort_by_measure(ppred::PopulationPredictionResult, r::AbstractBaseRiskMeasure;
190+
kwargs...)
191+
return sort(ppred.pred; by = x -> expected_risk(x, r; kwargs...),
192+
rev = bigger_is_better(r))
193+
end
194+
function quantile_by_measure(ppred::PopulationPredictionResult, r::AbstractBaseRiskMeasure,
195+
q::Real; kwargs...)
196+
sorted_predictions = sort_by_measure(ppred, r; kwargs...)
197+
filter!(x -> isa(x.res, OptimisationSuccess), sorted_predictions)
198+
return sorted_predictions[round(Int, quantile(length(sorted_predictions), q))]
199+
end
187200
function predict(res::NonFiniteAllocationOptimisationResult, rd::ReturnsResult)
188201
return PredictionResult(; res = res, rd = rd)
189202
end
@@ -248,4 +261,4 @@ function fit_and_predict(opt::NonFiniteAllocationOptimisationEstimator, rd::Retu
248261
end
249262

250263
export PredictionResult, MultiPeriodPredictionResult, PopulationPredictionResult, predict,
251-
fit_predict
264+
fit_predict, sort_by_measure

src/20_Optimisation/08_Base_JuMPOptimisation.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ function set_w!(model::JuMP.Model, X::MatNum, wi::Option{<:VecNum_VecVecNum})
6666
set_initial_w!(w, wi)
6767
return nothing
6868
end
69-
function process_model(model::JuMP.Model, ::JuMPOptimisationEstimator)
70-
if JuMP.termination_status(model) == JuMP.OPTIMIZE_NOT_CALLED
71-
return JuMPOptimisationSolution(; w = fill(NaN, length(model[:w])))
72-
end
69+
function process_model(model::JuMP.Model, ::OptimisationSuccess)
7370
k = JuMP.value(model[:k])
7471
ik = !iszero(k) ? inv(k) : 1
7572
w = JuMP.value.(model[:w]) * ik
7673
return JuMPOptimisationSolution(; w = w)
7774
end
75+
function process_model(model::JuMP.Model, ::OptimisationFailure)
76+
return JuMPOptimisationSolution(; w = fill(NaN, length(model[:w])))
77+
end
7878
function optimise_JuMP_model!(model::JuMP.Model, opt::JuMPOptimisationEstimator,
7979
datatype::DataType = Float64)
8080
trials = Dict()
@@ -115,7 +115,7 @@ function optimise_JuMP_model!(model::JuMP.Model, opt::JuMPOptimisationEstimator,
115115
@warn("Failed to solve optimisation problem. Check `retcode.res` for details.")
116116
OptimisationFailure(; res = trials)
117117
end
118-
return retcode, process_model(model, opt)
118+
return retcode, process_model(model, retcode)
119119
end
120120
function set_portfolio_returns!(model::JuMP.Model, X::MatNum)
121121
if haskey(model, :X)

0 commit comments

Comments
 (0)