Distributionally Robust Portfolio Optimization
distributionally Robust Return as Objective Term
using HiGHS
using Clarabel
using Distributions
using PortfolioOpt
using PortfolioOpt.TestUtils
Read Prices
prices = get_test_data();
numD, numA = size(prices) # A: Assets D: Days
(316, 6)
Calculating returns
returns_series = percentchange(prices);
315×6 TimeSeries.TimeArray{Float64, 2, Dates.Date, Matrix{Float64}} 2009-09-02 to 2010-12-01
┌────────────┬──────────────┬──────────────┬─────────────┬─────────────┬────────
│ │ AAPL │ BA │ DELL │ CAT │ EBAY ⋯
├────────────┼──────────────┼──────────────┼─────────────┼─────────────┼────────
│ 2009-09-02 │ -0.000725953 │ -0.00758663 │ 0.00920447 │ -0.00752737 │ -0.0 ⋯
│ 2009-09-03 │ 0.00829398 │ 0.00123967 │ -0.00651466 │ 0.0351643 │ 0.00 ⋯
│ 2009-09-04 │ 0.0225758 │ 0.0142385 │ 0.0288525 │ 0.0237567 │ 0.0 ⋯
│ 2009-09-08 │ 0.0153837 │ 0.00712106 │ 0.0172084 │ 0.0186511 │ -0.0 ⋯
│ 2009-09-09 │ -0.010351 │ 0.0208081 │ -0.00250627 │ 0.0306579 │ 0.0 ⋯
│ 2009-09-10 │ 0.0082973 │ -0.000791609 │ 0.040201 │ 0.00578393 │ 0.0 ⋯
│ 2009-09-11 │ -0.00231803 │ 0.0170331 │ 0.00241546 │ -0.0032861 │ 0.00 ⋯
│ 2009-09-14 │ 0.00906134 │ -0.00740019 │ -0.0126506 │ 0.00494539 │ 0.0 ⋯
│ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋱
│ 2010-11-22 │ 0.0216151 │ 0.00691933 │ 0.00431655 │ 0.00035727 │ 0.0 ⋯
│ 2010-11-23 │ -0.0147753 │ -0.0067156 │ -0.0100287 │ -0.0163095 │ -0.0 ⋯
│ 2010-11-24 │ 0.0196612 │ 0.0284591 │ 0.00434153 │ 0.0249304 │ 0.0 ⋯
│ 2010-11-26 │ 0.000635324 │ -0.00932579 │ -0.0165706 │ -0.00661235 │ -0.00 ⋯
│ 2010-11-29 │ 0.00593651 │ -0.00679012 │ -0.00586081 │ -0.00546773 │ -0.0 ⋯
│ 2010-11-30 │ -0.0180516 │ -0.00916718 │ -0.0257922 │ 0.0111151 │ -0.0 ⋯
│ 2010-12-01 │ 0.0168729 │ 0.0305786 │ 0.0143722 │ 0.0336879 │ 0.00 ⋯
└────────────┴──────────────┴──────────────┴─────────────┴─────────────┴────────
2 columns and 300 rows omitted
Backtest parameters
DEFAULT_SOLVER = optimizer_with_attributes(
HiGHS.Optimizer, "presolve" => "on", "time_limit" => 60.0, "log_to_console" => false
)
Clarabel_SOLVER = optimizer_with_attributes(
Clarabel.Optimizer, "verbose" => false, "max_iter" => 900000
)
date_range = timestamp(returns_series)[100:end];
216-element Vector{Dates.Date}:
2010-01-26
2010-01-27
2010-01-28
2010-01-29
2010-02-01
2010-02-02
2010-02-03
2010-02-04
2010-02-05
2010-02-08
⋮
2010-11-18
2010-11-19
2010-11-22
2010-11-23
2010-11-24
2010-11-26
2010-11-29
2010-11-30
2010-12-01
Backtest Delage's robust return
backtest_results = Dict()
backtest_results["Delage"], _ = sequential_backtest_market(
VolumeMarketHistory(returns_series), date_range,
) do market, past_returns, ext
numD, numA = size(past_returns)
returns = values(past_returns)
k_back = 80
Σ, r̄ = mean_variance(returns[(end - k_back):end, :])
d = MvNormal(r̄, Σ)
formulation = PortfolioFormulation(MAX_SENSE,
ObjectiveTerm(ExpectedUtility(MomentUncertainty(d, 0.05, 1.3),
PieceWiseUtility(
[1.0], [0.0]
)
))
)
pointers = change_bids!(market, formulation, Clarabel_SOLVER)
return pointers
end
(Dict{Symbol, PortfolioOpt.StateRecorder}(:decisions => PortfolioOpt.DecisionRecorder{Float64}(2-dimensional DenseAxisArray{Float64,2,...} with index sets:
Dimension 1, [Dates.Date("2010-01-26"), Dates.Date("2010-01-27"), Dates.Date("2010-01-28"), Dates.Date("2010-01-29"), Dates.Date("2010-02-01"), Dates.Date("2010-02-02"), Dates.Date("2010-02-03"), Dates.Date("2010-02-04"), Dates.Date("2010-02-05"), Dates.Date("2010-02-08") … Dates.Date("2010-11-17"), Dates.Date("2010-11-18"), Dates.Date("2010-11-19"), Dates.Date("2010-11-22"), Dates.Date("2010-11-23"), Dates.Date("2010-11-24"), Dates.Date("2010-11-26"), Dates.Date("2010-11-29"), Dates.Date("2010-11-30"), Dates.Date("2010-12-01")]
Dimension 2, 1:6
And data, a 216×6 Matrix{Float64}:
1.0446035529237261e-7 1.613867796203562e-7 … 1.7909538434631164e-6
1.9595030396546636e-7 1.084904202018651e-7 4.553832788424591e-6
1.5314184003658203e-6 5.230417130358743e-6 0.9999838333400731
5.275247805998895e-7 0.03867049846463573 0.9492064287014171
3.0934198261513346e-7 0.09791699329861743 0.8412845500473712
4.5159497107757124e-7 0.11768697985244408 … 0.8450649247739467
2.2041079319406903e-7 0.09634511195408313 0.8873842220166757
5.651966765221162e-7 0.0157133490619404 0.9867462221414217
6.783041401591429e-10 6.532787036841918e-9 1.085802721025627e-7
6.811610418677307e-8 1.7588744966393617e-7 6.994091342309392e-6
⋮ ⋱ ⋮
9.421715346705977e-8 5.5763072890628804e-8 0.2334775383319056
2.2137057175139616e-7 3.766518431422223e-8 0.10273824736795502
2.999573125835883e-7 5.437911863358717e-8 0.17295822942981595
4.3298577562013234e-7 1.2201566296382917e-7 … 0.09177592788894023
3.849616906702529e-6 2.396557155165589e-7 0.09019366388190699
8.53198466773854e-6 2.1156152551956715e-7 0.0553275999721009
2.681854461626518e-6 3.3962524131622585e-7 0.0797551574781441
0.04611145811990149 1.6111896235064493e-7 0.12589828543647005
0.09891373233087623 2.6877283565835096e-7 … 0.2015734967274358), :wealth => PortfolioOpt.WealthRecorder{Float64}(1-dimensional DenseAxisArray{Float64,1,...} with index sets:
Dimension 1, [Dates.Date("2010-01-26"), Dates.Date("2010-01-27"), Dates.Date("2010-01-28"), Dates.Date("2010-01-29"), Dates.Date("2010-02-01"), Dates.Date("2010-02-02"), Dates.Date("2010-02-03"), Dates.Date("2010-02-04"), Dates.Date("2010-02-05"), Dates.Date("2010-02-08") … Dates.Date("2010-11-18"), Dates.Date("2010-11-19"), Dates.Date("2010-11-22"), Dates.Date("2010-11-23"), Dates.Date("2010-11-24"), Dates.Date("2010-11-26"), Dates.Date("2010-11-29"), Dates.Date("2010-11-30"), Dates.Date("2010-12-01"), Dates.Date("2010-12-02")]
And data, a 217-element Vector{Float64}:
1.0
1.0000000281760482
1.00000017590688
0.9878791022917492
0.9392488057447699
0.9627567875060582
0.9837332342828432
1.0024638850773422
0.952748924248973
0.9527489227304625
⋮
1.1501669601643518
1.1419356138474503
1.151958708206736
1.133209540981234
1.1702422522914917
1.1686191982628253
1.1362621811543034
1.0997212280634965
1.1112574412388214), :returns => PortfolioOpt.ReturnsRecorder{Float64}(1-dimensional DenseAxisArray{Float64,1,...} with index sets:
Dimension 1, [Dates.Date("2010-01-26"), Dates.Date("2010-01-27"), Dates.Date("2010-01-28"), Dates.Date("2010-01-29"), Dates.Date("2010-02-01"), Dates.Date("2010-02-02"), Dates.Date("2010-02-03"), Dates.Date("2010-02-04"), Dates.Date("2010-02-05"), Dates.Date("2010-02-08") … Dates.Date("2010-11-17"), Dates.Date("2010-11-18"), Dates.Date("2010-11-19"), Dates.Date("2010-11-22"), Dates.Date("2010-11-23"), Dates.Date("2010-11-24"), Dates.Date("2010-11-26"), Dates.Date("2010-11-29"), Dates.Date("2010-11-30"), Dates.Date("2010-12-01")]
And data, a 216-element Vector{Float64}:
2.8176048171665645e-8
1.4773082759467454e-7
-0.01212107148295086
-0.04922697163464988
0.02502849257566837
0.021787898095346394
0.019040376132208117
-0.049592769942563675
-1.5938201946604013e-9
3.5923014607282966e-8
⋮
0.00451170579445364
-0.0071566534268427745
0.008777285021802078
-0.01627590215858428
0.03267949127766033
-0.0013869384954168556
-0.027688247083927184
-0.03215890988617233
0.010490125025265858)), nothing)
Backtest NingNing Du's Wasserstein robust return (under light tail assumption)
backtest_results["Wasserstein_light_tail"], _ = sequential_backtest_market(
VolumeMarketHistory(returns_series), date_range,
) do market, past_returns, ext
numD, numA = size(past_returns)
returns = values(past_returns)
j_robust = 30
δ = 0.1
ϵ = log(1/δ)/j_robust
d = DeterministicSamples(returns'[:, (end - j_robust):end])
s = DuWassersteinBall(d; ϵ=ϵ)
formulation = PortfolioFormulation(MAX_SENSE,
ObjectiveTerm(ExpectedReturn(s))
)
pointers = change_bids!(market, formulation, DEFAULT_SOLVER)
return pointers
end
(Dict{Symbol, PortfolioOpt.StateRecorder}(:decisions => PortfolioOpt.DecisionRecorder{Float64}(2-dimensional DenseAxisArray{Float64,2,...} with index sets:
Dimension 1, [Dates.Date("2010-01-26"), Dates.Date("2010-01-27"), Dates.Date("2010-01-28"), Dates.Date("2010-01-29"), Dates.Date("2010-02-01"), Dates.Date("2010-02-02"), Dates.Date("2010-02-03"), Dates.Date("2010-02-04"), Dates.Date("2010-02-05"), Dates.Date("2010-02-08") … Dates.Date("2010-11-17"), Dates.Date("2010-11-18"), Dates.Date("2010-11-19"), Dates.Date("2010-11-22"), Dates.Date("2010-11-23"), Dates.Date("2010-11-24"), Dates.Date("2010-11-26"), Dates.Date("2010-11-29"), Dates.Date("2010-11-30"), Dates.Date("2010-12-01")]
Dimension 2, 1:6
And data, a 216×6 Matrix{Float64}:
0.0 0.0 0.0 0.0 0.0 1.0
0.0 0.0 0.0 0.0 0.0 1.014505893019039
0.0 0.0 0.0 0.0 0.0 1.0471441523118772
0.0 0.0 0.0 0.0 0.0 1.034451495920218
0.0 0.0 0.0 0.0 0.0 0.9827742520398913
0.0 0.0 0.0 0.0 0.0 1.0081595648232098
0.0 0.0 0.0 0.0 0.0 1.0326382592928383
0.0 0.0 0.0 0.0 0.0 1.0553037171350865
0.0 0.0 0.0 0.0 0.0 1.0027198549410703
0.0 0.0 0.0 0.0 0.0 0.9891205802357208
⋮ ⋮
0.0 0.0 0.0 0.0 0.0 1.1199295540171785
0.0 0.0 0.0 0.0 1.0823300006448997 0.0
0.0 0.0 0.0 0.0 0.0 1.0727706581465637
0.0 0.0 0.0 0.0 1.0674990578608308 0.0
0.0 0.0 0.0 0.0 1.0514726359738862 0.0
0.0 0.0 0.0 0.0 1.08735788498161 0.0
0.0 0.0 0.0 0.0 1.0852674821267911 0.0
0.0 0.0 0.0 0.0 1.0532146383529022 0.0
0.0 0.0 0.0 0.0 0.0 1.0152389864903593), :wealth => PortfolioOpt.WealthRecorder{Float64}(1-dimensional DenseAxisArray{Float64,1,...} with index sets:
Dimension 1, [Dates.Date("2010-01-26"), Dates.Date("2010-01-27"), Dates.Date("2010-01-28"), Dates.Date("2010-01-29"), Dates.Date("2010-02-01"), Dates.Date("2010-02-02"), Dates.Date("2010-02-03"), Dates.Date("2010-02-04"), Dates.Date("2010-02-05"), Dates.Date("2010-02-08") … Dates.Date("2010-11-18"), Dates.Date("2010-11-19"), Dates.Date("2010-11-22"), Dates.Date("2010-11-23"), Dates.Date("2010-11-24"), Dates.Date("2010-11-26"), Dates.Date("2010-11-29"), Dates.Date("2010-11-30"), Dates.Date("2010-12-01"), Dates.Date("2010-12-02")]
And data, a 217-element Vector{Float64}:
1.0
1.014505893019039
1.0471441523118772
1.034451495920218
0.9827742520398913
1.0081595648232098
1.0326382592928383
1.0553037171350865
1.0027198549410703
0.9891205802357208
⋮
1.0823300006448997
1.0727706581465637
1.0674990578608308
1.0514726359738862
1.08735788498161
1.0852674821267911
1.0532146383529022
1.0152389864903593
1.048358451545252), :returns => PortfolioOpt.ReturnsRecorder{Float64}(1-dimensional DenseAxisArray{Float64,1,...} with index sets:
Dimension 1, [Dates.Date("2010-01-26"), Dates.Date("2010-01-27"), Dates.Date("2010-01-28"), Dates.Date("2010-01-29"), Dates.Date("2010-02-01"), Dates.Date("2010-02-02"), Dates.Date("2010-02-03"), Dates.Date("2010-02-04"), Dates.Date("2010-02-05"), Dates.Date("2010-02-08") … Dates.Date("2010-11-17"), Dates.Date("2010-11-18"), Dates.Date("2010-11-19"), Dates.Date("2010-11-22"), Dates.Date("2010-11-23"), Dates.Date("2010-11-24"), Dates.Date("2010-11-26"), Dates.Date("2010-11-29"), Dates.Date("2010-11-30"), Dates.Date("2010-12-01")]
And data, a 216-element Vector{Float64}:
0.014505893019038872
0.03217158176943748
-0.012121212121212248
-0.049956178790534884
0.02583025830258316
0.024280575539568545
0.021949078138718017
-0.049828178694157885
-0.013562386980108948
0.005499541704858329
⋮
-0.03357314148681014
-0.00883218842001981
-0.004914004914005337
-0.015013054830287144
0.034128561961563865
-0.0019224607497595274
-0.029534510433386705
-0.036056897122064425
0.032622333751568616)), nothing)
Plot
using Plots
using Plots.PlotMeasures
plt = plot(;title="Culmulative Wealth",
xlabel="Time",
ylabel="Wealth",
legend=:outertopright,
left_margin=10mm
);
for (strategy_name, recorders) in backtest_results
plot!(plt,
axes(get_records(recorders[:wealth]), 1), get_records(recorders[:wealth]).data;
label=strategy_name,
)
end
plt
This page was generated using Literate.jl.