Stochastic Portfolio Optimization
Empirical Forecast
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(
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 Markowitz
backtest_results = Dict()
backtest_results["EP_markowitz_limit_var"], _ = sequential_backtest_market(
VolumeMarketHistory(returns_series), date_range,
) do market, past_returns, ext
max_std = 0.003 / market_budget(market)
k_back = 60
numD, numA = size(past_returns)
returns = values(past_returns)
Σ, r̄ = mean_variance(returns[(end - k_back):end, :])
d = MvNormal(r̄, Σ)
formulation = PortfolioFormulation(MAX_SENSE,
ObjectiveTerm(ExpectedReturn(d)),
RiskConstraint(SqrtVariance(d), LessThan(max_std)),
)
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}:
4.36648728667376e-9 0.05147102022331901 … 0.09208739135995249
4.441139266035086e-9 0.0485328353212699 0.09428049411322754
6.367552206815893e-10 0.04873958041716562 0.09381704711308365
2.915443576502887e-9 0.05865046456267625 0.09189344890754501
6.13154275989476e-10 0.05285985758323034 0.09340665473537316
6.590893674562587e-9 0.06204938724021083 … 0.09314985686415869
1.5796212294329877e-8 0.050735279365279246 0.1009835808643875
5.359485934028098e-9 0.03243789954844055 0.1123122687815134
7.579223872697886e-9 0.010195261313015671 0.11832076277407119
2.2967358899691538e-9 0.011778998929513652 0.1188574902032035
⋮ ⋱ ⋮
0.04580559881625895 2.4194958454468882e-9 0.08561076820499285
0.07810358917303062 4.367850852336765e-9 0.06716540010482325
0.05930492030648491 1.6537257677238027e-9 0.0699472848651464
0.07327135807767597 4.861880603156304e-9 … 0.06490995836719034
0.0829418312892816 2.436610573629674e-9 0.05739234201792855
0.057010295436139904 1.0782484514699301e-9 0.05727990299937209
0.06407299722627212 1.0938607986396446e-9 0.06610396546443759
0.08804453329647226 1.620280855236644e-9 0.07205767109843356
0.06257441869513483 9.24939157255311e-10 … 0.07308891273021623), :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.0012734543074289
1.0078555333543904
1.007127412488936
1.000699251964002
1.00409272050154
1.0065763226117626
1.0083997081174814
1.0016739192545618
0.999911088389366
⋮
1.0663364277932836
1.0659911475050354
1.0677488464596292
1.0630550140672106
1.0688436502129328
1.0689730632813268
1.066805784841721
1.0627353564706847
1.068431655913844), :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.0012734543074288459
0.006573707730536204
-0.0007224456694016219
-0.006382668612949358
0.0033910973060867766
0.0024734788526123586
0.0018114726769925067
-0.006669764785508895
-0.001759884959875604
0.0005508460929085164
⋮
-0.00048475245681168524
-0.00032380051853128135
0.001648887008778243
-0.004396007926379004
0.005445283705097381
0.00012107764158790591
-0.0020274397120476767
-0.0038155289640093393
0.005360035693247825)), nothing)
Backtest Sampled CVAR
backtest_results["EP_limit_cvar"], _ = sequential_backtest_market(
VolumeMarketHistory(returns_series), date_range,
) do market, past_returns, ext
numD, numA = size(past_returns)
returns = values(past_returns)
R = -0.001 / market_budget(market)
d = DeterministicSamples(returns'[:,:])
formulation = PortfolioFormulation(MAX_SENSE,
ObjectiveTerm(ExpectedReturn(d)),
RiskConstraint(ConditionalExpectedReturn(d), GreaterThan(R)),
)
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}:
3.156705173203655e-11 2.011206413880008e-11 … 0.999999999877939
3.363829866226616e-11 1.2675767414518414e-11 1.0145058928975075
5.584597668550604e-12 3.7674934578334974e-12 1.047144152281842
1.9235725457346746e-11 4.874912244621803e-11 1.0344514957938216
1.7908761289055337e-10 6.143609328754888e-10 0.9827742499064522
7.875439354171189e-11 2.395695642712488e-10 … 1.0081595642879428
1.8257631481652054e-11 4.671718764542464e-11 1.0326382591307754
1.1947149957287435e-10 3.206335069378413e-10 1.0553037156506275
7.959277953734981e-12 1.8308777144336866e-11 1.0027198548810865
1.5806600490148677e-11 2.096122403030381e-11 0.9891205801562638
⋮ ⋱ ⋮
6.639786788334552e-9 4.1557089425922377e-11 1.3177332336786463
6.731252919192307e-8 3.313747524184396e-10 1.273492725382155
1.7436330552679384e-9 2.3692872442077225e-11 1.286132921661363
3.787732647793867e-8 1.882375971696925e-10 … 1.2798128179422175
4.0967318228130505e-8 1.740877957451379e-10 1.240312421515505
1.3298926494665256e-8 6.121478614571412e-11 1.2600626481399533
3.340025305018836e-8 1.5223224029809767e-10 1.2719127475154368
1.1863306990338503e-8 2.8345009122925747e-10 1.2687526901441222
6.263473631420252e-10 7.320126696149965e-12 … 1.2592726550957858), :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.0145058930179014
1.0471441523062617
1.0344514959145228
0.9827742520378777
1.008159564795834
1.0326382592549634
1.0553037170936874
1.0027198549365555
0.9891205802321483
⋮
1.2734927993050238
1.2861329252906941
1.2798128618325115
1.2403124655643274
1.2600626642388557
1.2719127831769248
1.268752751687212
1.2592726571728396
1.3003530700705106), :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.014505893017901424
0.032171581765059715
-0.012121212121353338
-0.0499561787872509
0.025830258276829283
0.02428057552981391
0.02194907813611017
-0.04982817866116128
-0.013562386979230259
0.005499541703913746
⋮
-0.03357314105296851
0.009925557484556063
-0.0049140048698730465
-0.030864196982381627
0.015923567022719763
0.009404388586679767
-0.0024844718376207456
-0.00747197947099255
0.03262233374454392)), 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.