Skip to content

Commit b9bc74e

Browse files
committed
Finish and test expected risk of predictions
1 parent c033470 commit b9bc74e

File tree

8 files changed

+174
-39
lines changed

8 files changed

+174
-39
lines changed

src/19_RiskMeasures/01_Base_RiskMeasures.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -527,10 +527,9 @@ function solver_selector(::Nothing, ::Nothing)
527527
throw(ArgumentError("Both risk_solver and prior_solver are nothing, cannot solve JuMP model."))
528528
end
529529
function expected_risk end
530-
function predicted_risk end
531530
function no_bounds_risk_measure end
532531
function no_bounds_no_risk_expr_risk_measure end
533532

534533
export Frontier, RiskMeasureSettings, HierarchicalRiskMeasureSettings, SumScalariser,
535-
MaxScalariser, MinScalariser, LogSumExpScalariser, expected_risk, predicted_risk,
536-
RiskMeasure, HierarchicalRiskMeasure
534+
MaxScalariser, MinScalariser, LogSumExpScalariser, expected_risk, RiskMeasure,
535+
HierarchicalRiskMeasure

src/19_RiskMeasures/03_MomentRiskMeasures.jl

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,9 @@ function calc_moment_target(::LoHiOrderMoment{<:Any, Nothing, Nothing, <:Any}, :
828828
x::VecNum)
829829
return Statistics.mean(x)
830830
end
831+
function calc_prediction_moment_target(::LoHiOrderMoment, X::VecNum)
832+
return Statistics.mean(X)
833+
end
831834
"""
832835
calc_moment_target(r::LoHiOrderMoment{<:Any, <:StatsBase.AbstractWeights, Nothing, <:Any},
833836
::Any, x::VecNum)
@@ -854,6 +857,11 @@ function calc_moment_target(r::LoHiOrderMoment{<:Any, <:StatsBase.AbstractWeight
854857
<:Any}, ::Any, x::VecNum)
855858
return Statistics.mean(x, r.w)
856859
end
860+
function calc_prediction_moment_target(r::LoHiOrderMoment{<:Any,
861+
<:StatsBase.AbstractWeights,
862+
Nothing, <:Any}, X::VecNum)
863+
return Statistics.mean(X, r.w)
864+
end
857865
"""
858866
calc_moment_target(r::LoHiOrderMoment{<:Any, <:Any, <:VecNum, <:Any},
859867
w::VecNum, ::Any)
@@ -932,6 +940,10 @@ Compute the target value for moment calculations when the risk measure provides
932940
function calc_moment_target(r::LoHiOrderMoment{<:Any, <:Any, <:Number, <:Any}, ::Any, ::Any)
933941
return r.mu
934942
end
943+
function calc_prediction_moment_target(r::LoHiOrderMoment{<:Any, <:Any, <:Number, <:Any},
944+
::Any)
945+
return r.mu
946+
end
935947
"""
936948
calc_deviations_vec(r::LoHiOrderMoment, w::VecNum,
937949
X::MatNum; fees::Option{<:Fees} = nothing)
@@ -968,23 +980,39 @@ function calc_deviations_vec(r::LoHiOrderMoment, w::VecNum, X::MatNum,
968980
tgt = calc_moment_target(r, w, x)
969981
return x .- tgt
970982
end
983+
function calc_prediction_deviations_vec(r::LoHiOrderMoment, X::VecNum)
984+
return X .- calc_prediction_moment_target(r, X)
985+
end
971986
function (r::LowOrderMoment{<:Any, <:Any, <:Any, <:FirstLowerMoment})(w::VecNum, X::MatNum,
972987
fees::Option{<:Fees} = nothing)
973988
val = min.(calc_deviations_vec(r, w, X, fees), zero(eltype(X)))
974989
return isnothing(r.w) ? -Statistics.mean(val) : -Statistics.mean(val, r.w)
975990
end
991+
function (r::LowOrderMoment{<:Any, <:Any, <:Any, <:FirstLowerMoment})(X::VecNum)
992+
val = min.(calc_prediction_deviations_vec(r, X), zero(eltype(X)))
993+
return isnothing(r.w) ? -Statistics.mean(val) : -Statistics.mean(val, r.w)
994+
end
976995
function (r::LowOrderMoment{<:Any, <:Any, <:Any, <:MeanAbsoluteDeviation})(w::VecNum,
977996
X::MatNum,
978997
fees::Option{<:Fees} = nothing)
979998
val = abs.(calc_deviations_vec(r, w, X, fees))
980999
return isnothing(r.w) ? Statistics.mean(val) : Statistics.mean(val, r.w)
9811000
end
1001+
function (r::LowOrderMoment{<:Any, <:Any, <:Any, <:MeanAbsoluteDeviation})(X::VecNum)
1002+
val = abs.(calc_prediction_deviations_vec(r, X))
1003+
return isnothing(r.w) ? -Statistics.mean(val) : -Statistics.mean(val, r.w)
1004+
end
9821005
function (r::HighOrderMoment{<:Any, <:Any, <:Any, <:ThirdLowerMoment})(w::VecNum, X::MatNum,
9831006
fees::Option{<:Fees} = nothing)
9841007
val = min.(calc_deviations_vec(r, w, X, fees), zero(eltype(X)))
9851008
val .= val .^ 3
9861009
return isnothing(r.w) ? -Statistics.mean(val) : -Statistics.mean(val, r.w)
9871010
end
1011+
function (r::HighOrderMoment{<:Any, <:Any, <:Any, <:ThirdLowerMoment})(X::VecNum)
1012+
val = min.(calc_prediction_deviations_vec(r, X), zero(eltype(X)))
1013+
val .= val .^ 3
1014+
return isnothing(r.w) ? -Statistics.mean(val) : -Statistics.mean(val, r.w)
1015+
end
9881016
function (r::HighOrderMoment{<:Any, <:Any, <:Any,
9891017
<:StandardisedHighOrderMoment{<:Any, <:ThirdLowerMoment}})(w::VecNum,
9901018
X::MatNum,
@@ -995,48 +1023,86 @@ function (r::HighOrderMoment{<:Any, <:Any, <:Any,
9951023
res = isnothing(r.w) ? -Statistics.mean(val) : -Statistics.mean(val, r.w)
9961024
return res / (sigma * sqrt(sigma))
9971025
end
1026+
function (r::HighOrderMoment{<:Any, <:Any, <:Any,
1027+
<:StandardisedHighOrderMoment{<:Any, <:ThirdLowerMoment}})(X::VecNum)
1028+
val = min.(calc_prediction_deviations_vec(r, X), zero(eltype(X)))
1029+
sigma = Statistics.var(r.alg.ve, val; mean = zero(eltype(val)))
1030+
val .= val .^ 3
1031+
res = isnothing(r.w) ? -Statistics.mean(val) : -Statistics.mean(val, r.w)
1032+
return res / (sigma * sqrt(sigma))
1033+
end
9981034
function (r::LowOrderMoment{<:Any, <:Any, <:Any,
9991035
<:SecondMoment{<:Any, <:Semi, <:SOCRiskExpr}})(w::VecNum,
10001036
X::MatNum,
10011037
fees::Option{<:Fees} = nothing)
10021038
val = min.(calc_deviations_vec(r, w, X, fees), zero(eltype(X)))
10031039
return Statistics.std(r.alg.ve, val; mean = zero(eltype(val)))
10041040
end
1041+
function (r::LowOrderMoment{<:Any, <:Any, <:Any,
1042+
<:SecondMoment{<:Any, <:Semi, <:SOCRiskExpr}})(X::VecNum)
1043+
val = min.(calc_prediction_deviations_vec(r, X), zero(eltype(X)))
1044+
return Statistics.std(r.alg.ve, val; mean = zero(eltype(val)))
1045+
end
10051046
function (r::LowOrderMoment{<:Any, <:Any, <:Any,
10061047
<:SecondMoment{<:Any, <:Semi, <:QuadSecondMomentFormulations}})(w::VecNum,
10071048
X::MatNum,
10081049
fees::Option{<:Fees} = nothing)
10091050
val = min.(calc_deviations_vec(r, w, X, fees), zero(eltype(X)))
10101051
return Statistics.var(r.alg.ve, val; mean = zero(eltype(val)))
10111052
end
1053+
function (r::LowOrderMoment{<:Any, <:Any, <:Any,
1054+
<:SecondMoment{<:Any, <:Semi, <:QuadSecondMomentFormulations}})(X::VecNum)
1055+
val = min.(calc_prediction_deviations_vec(r, X), zero(eltype(X)))
1056+
return Statistics.var(r.alg.ve, val; mean = zero(eltype(val)))
1057+
end
10121058
function (r::LowOrderMoment{<:Any, <:Any, <:Any,
10131059
<:SecondMoment{<:Any, <:Full, <:SOCRiskExpr}})(w::VecNum,
10141060
X::MatNum,
10151061
fees::Option{<:Fees} = nothing)
10161062
val = calc_deviations_vec(r, w, X, fees)
10171063
return Statistics.std(r.alg.ve, val; mean = zero(eltype(val)))
10181064
end
1065+
function (r::LowOrderMoment{<:Any, <:Any, <:Any,
1066+
<:SecondMoment{<:Any, <:Full, <:SOCRiskExpr}})(X::VecNum)
1067+
val = calc_prediction_deviations_vec(r, X)
1068+
return Statistics.std(r.alg.ve, val; mean = zero(eltype(val)))
1069+
end
10191070
function (r::LowOrderMoment{<:Any, <:Any, <:Any,
10201071
<:SecondMoment{<:Any, <:Full, <:QuadSecondMomentFormulations}})(w::VecNum,
10211072
X::MatNum,
10221073
fees::Option{<:Fees} = nothing)
10231074
val = calc_deviations_vec(r, w, X, fees)
10241075
return Statistics.var(r.alg.ve, val; mean = zero(eltype(val)))
10251076
end
1077+
function (r::LowOrderMoment{<:Any, <:Any, <:Any,
1078+
<:SecondMoment{<:Any, <:Full, <:QuadSecondMomentFormulations}})(X::VecNum)
1079+
val = calc_prediction_deviations_vec(r, X)
1080+
return Statistics.var(r.alg.ve, val; mean = zero(eltype(val)))
1081+
end
10261082
function (r::HighOrderMoment{<:Any, <:Any, <:Any, <:FourthMoment{<:Semi}})(w::VecNum,
10271083
X::MatNum,
10281084
fees::Option{<:Fees} = nothing)
10291085
val = min.(calc_deviations_vec(r, w, X, fees), zero(eltype(X)))
10301086
val .= val .^ 4
10311087
return isnothing(r.w) ? Statistics.mean(val) : Statistics.mean(val, r.w)
10321088
end
1089+
function (r::HighOrderMoment{<:Any, <:Any, <:Any, <:FourthMoment{<:Semi}})(X::VecNum)
1090+
val = min.(calc_prediction_deviations_vec(r, X), zero(eltype(X)))
1091+
val .= val .^ 4
1092+
return isnothing(r.w) ? Statistics.mean(val) : Statistics.mean(val, r.w)
1093+
end
10331094
function (r::HighOrderMoment{<:Any, <:Any, <:Any, <:FourthMoment{<:Full}})(w::VecNum,
10341095
X::MatNum,
10351096
fees::Option{<:Fees} = nothing)
10361097
val = calc_deviations_vec(r, w, X, fees)
10371098
val .= val .^ 4
10381099
return isnothing(r.w) ? Statistics.mean(val) : Statistics.mean(val, r.w)
10391100
end
1101+
function (r::HighOrderMoment{<:Any, <:Any, <:Any, <:FourthMoment{<:Full}})(X::VecNum)
1102+
val = calc_prediction_deviations_vec(r, X)
1103+
val .= val .^ 4
1104+
return isnothing(r.w) ? Statistics.mean(val) : Statistics.mean(val, r.w)
1105+
end
10401106
function (r::HighOrderMoment{<:Any, <:Any, <:Any,
10411107
<:StandardisedHighOrderMoment{<:Any, <:FourthMoment{<:Semi}}})(w::VecNum,
10421108
X::MatNum,
@@ -1048,10 +1114,8 @@ function (r::HighOrderMoment{<:Any, <:Any, <:Any,
10481114
return res / sigma^2
10491115
end
10501116
function (r::HighOrderMoment{<:Any, <:Any, <:Any,
1051-
<:StandardisedHighOrderMoment{<:Any, <:FourthMoment{<:Full}}})(w::VecNum,
1052-
X::MatNum,
1053-
fees::Option{<:Fees} = nothing)
1054-
val = calc_deviations_vec(r, w, X, fees)
1117+
<:StandardisedHighOrderMoment{<:Any, <:FourthMoment{<:Full}}})(X::VecNum)
1118+
val = calc_prediction_deviations_vec(r, X)
10551119
sigma = Statistics.var(r.alg.ve, val; mean = zero(eltype(val)))
10561120
val .= val .^ 4
10571121
res = isnothing(r.w) ? Statistics.mean(val) : Statistics.mean(val, r.w)

src/19_RiskMeasures/18_TrackingRiskMeasure.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ function (r::TrackingRiskMeasure)(w::VecNum, X::MatNum, fees::Option{<:Fees} = n
5353
benchmark = tracking_benchmark(r.tr, X)
5454
return norm_tracking(r.alg, calc_net_returns(w, X, fees), benchmark, size(X, 1))
5555
end
56+
function (r::TrackingRiskMeasure{ReturnsTracking})(X::VecNum)
57+
benchmark = tracking_benchmark(r.tr, X)
58+
return norm_tracking(r.alg, X, benchmark, length(X))
59+
end
60+
function (r::TrackingRiskMeasure{WeightsTracking})(::VecNum)
61+
throw(MethodError(r,
62+
"Tracking risk measure using the `WeightsTracking` algorithm cannot be computed for a prediction of portfolio returns because there are no weights."))
63+
end
5664
function risk_measure_view(r::TrackingRiskMeasure, i, args...)
5765
tr = tracking_view(r.tr, i)
5866
return TrackingRiskMeasure(; settings = r.settings, tr = tr, alg = r.alg)

src/19_RiskMeasures/25_ExpectedRisk.jl

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,6 @@ end
6161
function expected_risk(r::AbstractBaseRiskMeasure, w::VecVecNum, args...; kwargs...)
6262
return [expected_risk(r, wi, args...; kwargs...) for wi in w]
6363
end
64-
function predicted_risk(r::Union{<:ERkNetRet, <:ERkwXFees}, X::VecNum; kwargs...)
65-
return r(X)
66-
end
67-
function predicted_risk(r::AbstractBaseRiskMeasure, X::VecVecNum; kwargs...)
68-
return [r(Xi; kwargs...) for Xi in X]
69-
end
70-
function predicted_risk(r::RkRatioRM, X::VecNum; kwargs...)
71-
return predicted_risk(r.r1, X; kwargs...) / predicted_risk(r.r2, X; kwargs...)
72-
end
73-
function predicted_risk(r::ERkw, args...; kwargs...)
74-
throw(MethodError(predicted_risk,
75-
"cannot be computed for $r because the risk measure uses internal quantities and asset weights which are not defined for the result of a prediction of portfolio returns. For:\n- `StandardDeviation`: `LowOrderMoment(; alg = SecondMoment(; alg = Full(), alg2 = SOCRiskExpr())`.\n- Semi `StandardDeviation`: `LowOrderMoment(; alg = SecondMoment(; alg = Semi(), alg2 = SOCRiskExpr())`.\n- Variance: `LowOrderMoment(; alg = SecondMoment(; alg = Full(), alg2 = QuadRiskExpr())`, `alg2` can also be `SquaredSOCRiskExpr()` or `RSOCRiskExpr()`.\n- Semi Variance: `LowOrderMoment(; alg = SecondMoment(; alg = Semi(), alg2 = QuadRiskExpr())`, `alg2` can also be `SquaredSOCRiskExpr()` or `RSOCRiskExpr()`.\n- UncertaintySetVariance: use one of the variance recommendations.\n- NegativeSkewness: the closest is to use `Skewness`.\n- TurnoverRiskMeasure and EqualRiskMeasure: not applicable."))
76-
end
7764
function number_effective_assets(w::VecNum)
7865
return inv(LinearAlgebra.dot(w, w))
7966
end

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

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,23 @@ function PredictionResult(; res::NonFiniteAllocationOptimisationResult,
9494
rd::PredictionReturnsResult)
9595
return PredictionResult(res, rd)
9696
end
97+
struct SingletonVector{T} <: AbstractVector{T} end
98+
Base.length(::SingletonVector) = 1
99+
Base.getindex(::SingletonVector, args...) = 1
100+
Base.:*(M::AbstractMatrix, ::SingletonVector) = dropdims(M; dims = 2)
101+
Base.size(::SingletonVector) = (1,)
102+
function expected_risk(pred::PredictionResult{<:Any,
103+
<:PredictionReturnsResult{<:Any, <:VecNum}},
104+
r::AbstractBaseRiskMeasure; kwargs...)
105+
return expected_risk(r, SingletonVector{Int}(), reshape(pred.rd.X, :, 1); kwargs...)
106+
end
107+
function expected_risk(pred::PredictionResult{<:Any,
108+
<:PredictionReturnsResult{<:Any, <:VecVecNum}},
109+
r::AbstractBaseRiskMeasure; kwargs...)
110+
X = pred.rd.X
111+
return [expected_risk(r, SingletonVector{Int}(), reshape(Xi, :, 1); kwargs...)
112+
for Xi in X]
113+
end
97114
struct MultiPeriodPredictionResult{T1} <: AbstractResult
98115
pred::T1
99116
function MultiPeriodPredictionResult(pred::AbstractVector{<:PredictionResult})
@@ -105,10 +122,21 @@ function MultiPeriodPredictionResult(;
105122
0))
106123
return MultiPeriodPredictionResult(pred)
107124
end
125+
function mapreduce_X(rd::AbstractVector{<:PredictionReturnsResult{<:Any, <:VecNum}})
126+
return mapreduce(x -> getproperty(x, :X), vcat, rd)
127+
end
128+
function mapreduce_X(rd::AbstractVector{<:PredictionReturnsResult{<:Any, <:VecVecNum}})
129+
N = length(rd[1].X)
130+
X = [eltype(rd[1].X[1])[] for _ in 1:N]
131+
for i in 1:N
132+
X[i] = mapreduce(x -> getproperty(x, :X)[i], vcat, rd)
133+
end
134+
return X
135+
end
108136
function get_multiperiod_returns_result(mpred::MultiPeriodPredictionResult)
109137
rd = mpred.rd
110138
nx = rd[1].nx
111-
X = mapreduce(x -> getproperty(x, :X), vcat, rd)
139+
X = mapreduce_X(rd)
112140
nf = rd[1].nf
113141
F = isnothing(rd[1].F) ? nothing : mapreduce(x -> getproperty(x, :F), vcat, rd)
114142
ts = isnothing(rd[1].ts) ? nothing : mapreduce(x -> getproperty(x, :ts), vcat, rd)
@@ -128,6 +156,18 @@ function Base.getproperty(mpred::MultiPeriodPredictionResult, sym::Symbol)
128156
getfield(mpred, sym)
129157
end
130158
end
159+
function _prediction_expected_risk(r::AbstractBaseRiskMeasure, X::VecNum; kwargs...)
160+
return expected_risk(r, SingletonVector{Int}(), reshape(X, :, 1); kwargs...)
161+
end
162+
function _prediction_expected_risk(r::AbstractBaseRiskMeasure, X::VecVecNum; kwargs...)
163+
return [expected_risk(r, SingletonVector{Int}(), reshape(Xi, :, 1); kwargs...)
164+
for Xi in X]
165+
end
166+
function expected_risk(mpred::MultiPeriodPredictionResult, r::AbstractBaseRiskMeasure;
167+
kwargs...)
168+
X = mpred.mrd.X
169+
return _prediction_expected_risk(r, X; kwargs...)
170+
end
131171
const PredRes_MultiPredRes = Union{<:PredictionResult, <:MultiPeriodPredictionResult}
132172
struct PopulationPredictionResult{T1} <: AbstractResult
133173
pred::T1
@@ -140,6 +180,10 @@ function PopulationPredictionResult(;
140180
0))
141181
return PopulationPredictionResult(pred)
142182
end
183+
function expected_risk(ppred::PopulationPredictionResult, r::AbstractBaseRiskMeasure;
184+
kwargs...)
185+
return [expected_risk(pred, r; kwargs...) for pred in ppred.pred]
186+
end
143187
function predict(res::NonFiniteAllocationOptimisationResult, rd::ReturnsResult)
144188
return PredictionResult(; res = res, rd = rd)
145189
end

src/21_ExpectedReturns.jl

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -376,19 +376,6 @@ function expected_risk(r::ReturnRiskMeasure, w::VecNum, pr::AbstractPriorResult,
376376
fees::Option{<:Fees} = nothing; kwargs...)
377377
return expected_return(r.rt, w, pr, fees)
378378
end
379-
function predicted_risk(r::ReturnRiskMeasure{<:ArithmeticReturn}, X::AbstractVector;
380-
kwargs...)
381-
return Statistics.mean(X)
382-
end
383-
function predicted_risk(r::ReturnRiskMeasure{<:LogarithmicReturn}, X::AbstractVector;
384-
kwargs...)
385-
w = r.rt.w
386-
return if isnothing(w)
387-
Statistics.mean(log1p.(X))
388-
else
389-
Statistics.mean(log1p.(X), w)
390-
end
391-
end
392379
"""
393380
struct ReturnRiskRatioRiskMeasure{T1, T2, T3} <: NonOptimisationRiskMeasure
394381
rt::T1
@@ -619,9 +606,6 @@ function expected_risk(r::ReturnRiskRatioRiskMeasure, w::VecNum, pr::AbstractPri
619606
fees::Option{<:Fees} = nothing; kwargs...)
620607
return expected_ratio(r.rk, r.rt, w, pr, fees; rf = r.rf, kwargs...)
621608
end
622-
function predicted_risk(r::ReturnRiskRatioRiskMeasure, X::AbstractVector; kwargs...)
623-
return (predicted_risk(r.rt, X; kwargs...) - r.rf) / predicted_risk(r.rk, X; kwargs...)
624-
end
625609
const PerfRM = Union{<:MeanReturn, <:ReturnRiskMeasure, <:ReturnRiskRatioRiskMeasure}
626610
"""
627611
brinson_attribution(X::TimeArray, w::VecNum, wb::VecNum,

0 commit comments

Comments
 (0)