Distributionally Robust Portfolio Optimization
distributionally Robust Return as Objective Term
using HiGHS
using Clarabel
using Distributions
using PortfolioOpt
using PortfolioOpt.TestUtilsRead 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 omittedBacktest 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-01Backtest 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.0446035529238486e-7 1.6138677962036675e-7 … 1.7909538434629792e-6
1.9595030396539483e-7 1.0849042020182421e-7 4.553832788423345e-6
1.5314183936883542e-6 5.230417106435837e-6 0.9999838333401446
5.275251547616286e-7 0.038670498561696756 0.9492064286030465
3.093419826294679e-7 0.09791699329878623 0.841284550049047
4.515949711380667e-7 0.11768697985263832 … 0.8450649247756528
2.2041080388927448e-7 0.09634511197079534 0.8873842220017293
5.65196672431233e-7 0.015713349064952815 0.9867462221399228
6.783041401583405e-10 6.532787036839921e-9 1.0858027210250551e-7
6.811610418678869e-8 1.7588744966396973e-7 6.994091342307946e-6
⋮ ⋱ ⋮
9.421719663156232e-8 5.576307214207446e-8 0.23347753837488613
2.2137055615545522e-7 3.766518005886245e-8 0.10273824736251057
2.999573127939947e-7 5.437911865715581e-8 0.1729582294252451
4.3298551940071007e-7 1.2201567527958584e-7 … 0.09177592786040506
3.849616907111671e-6 2.396557156276092e-7 0.0901936638793415
8.531980867345937e-6 2.1156144822289596e-7 0.05532759982692901
2.6818543349039953e-6 3.39625222672151e-7 0.0797551574356618
0.04611145811819807 1.6111896213876369e-7 0.1258982854328859
0.09891373230200771 2.687728296015252e-7 … 0.20157349671590005), :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.9878791022917486
0.9392488057466241
0.9627567875079588
0.983733234284786
1.0024638850788277
0.9527489242504287
0.9527489227319182
⋮
1.1501669601334692
1.1419356138167378
1.1519587081757525
1.1332095409511684
1.1702422522604468
1.1686191982301928
1.1362621811214757
1.0997212280317192
1.1112574412062064), :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.8176048171663604e-8
1.4773082759463338e-7
-0.012121071482951495
-0.04922697163277237
0.02502849257566831
0.0217878980953472
0.019040376131705644
-0.049592769942519876
-1.5938201946571453e-9
3.5923014607218135e-8
⋮
0.004511705792416523
-0.0071566534268869345
0.008777285021800937
-0.016275902158225167
0.032679491277663064
-0.0013869384968103323
-0.027688247084867328
-0.03215890988617699
0.010490125024807046)), 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
pltThis page was generated using Literate.jl.