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.366487243347157e-9   0.051471020223320806   …  0.09208739135995175
 4.441139431008122e-9   0.04853283532126676       0.09428049411322846
 6.367552375655185e-10  0.04873958041716492       0.09381704711308525
 2.915443424032201e-9   0.05865046456267631       0.09189344890754525
 6.131542967966947e-10  0.05285985758323522       0.09340665473536365
 6.590893487710306e-9   0.062049387240225753   …  0.09314985686413274
 1.5796212196434964e-8  0.05073527936527546       0.10098358086439349
 5.359486058766986e-9   0.03243789954844179       0.11231226878151233
 7.579223811107884e-9   0.010195261313021021      0.11832076277406893
 2.2967358698637423e-9  0.01177899892950056       0.11885749020320997
 ⋮                                             ⋱  ⋮
 0.045805598816261214   2.4194958490360625e-9     0.08561076820499741
 0.07810358917302992    4.367850929777909e-9      0.06716540010482776
 0.05930492030645432    1.6537257663198853e-9     0.06994728486515317
 0.07327135807767554    4.861880664745549e-9   …  0.06490995836719536
 0.08294183128949724    2.436610704955135e-9      0.0573923420178588
 0.057010295436011986   1.0782484341413406e-9     0.05727990299946496
 0.06407299722634069    1.0938608232641959e-9     0.06610396546443205
 0.08804453329668337    1.6202809077256874e-9     0.07205767109839953
 0.0625744186951413     9.249391862189811e-10  …  0.07308891273022117), :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.0078555333543902
 1.007127412488936
 1.000699251964002
 1.0040927205015404
 1.0065763226117619
 1.0083997081174805
 1.001673919254561
 0.9999110883893652
 ⋮
 1.0663364277932357
 1.0659911475049875
 1.0677488464595803
 1.0630550140671615
 1.0688436502128842
 1.0689730632812784
 1.0668057848416723
 1.0627353564706306
 1.0684316559137899), :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.0012734543074288207
  0.006573707730535992
 -0.0007224456694013908
 -0.006382668612949351
  0.003391097306087074
  0.0024734788526112068
  0.0018114726769923922
 -0.006669764785509027
 -0.0017598849598756618
  0.0005508460929086674
  ⋮
 -0.000484752456811761
 -0.00032380051853123884
  0.0016488870087775034
 -0.0043960079263794345
  0.005445283705098127
  0.00012107764158812885
 -0.0020274397120479907
 -0.0038155289640146167
  0.005360035693248103)), 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.1567043375203975e-11  2.0112058401432956e-11  …  0.9999999998779389
 3.3638301295518935e-11  1.2675768621411662e-11     1.014505892897507
 5.5845932992526734e-12  3.7674903581490166e-12     1.0471441522818419
 1.923572700533939e-11   4.874912602088713e-11      1.034451495793822
 1.7908761428534175e-10  6.143609385664203e-10      0.9827742499064525
 7.875438988219784e-11   2.395695535618274e-10   …  1.0081595642879428
 1.825762934029435e-11   4.6717182752391856e-11     1.0326382591307754
 1.194714956217017e-10   3.206334945717336e-10      1.0553037156506275
 7.959272995485972e-12   1.8308766988467514e-11     1.0027198548810867
 1.5806596233966034e-11  2.0961218559998176e-11     0.9891205801562638
 ⋮                                               ⋱  ⋮
 6.639853347942747e-9    4.1557515250147524e-11     1.3177332336785483
 6.73125313805476e-8     3.313747630742742e-10      1.2734927253821577
 1.743638508189528e-9    2.3692948865558733e-11     1.2861329216613564
 3.7877172175396645e-8   1.8823681401579372e-10  …  1.279812817942401
 4.0967318027527036e-8   1.7408779486698868e-10     1.2403124215155066
 1.32990594891597e-8     6.121540928107645e-11      1.260062648139794
 3.340029358693889e-8    1.522324298895472e-10      1.2719127475153937
 1.186329625879939e-8    2.8344982853943345e-10     1.2687526901441781
 6.263432064719233e-10   7.320076996334804e-12   …  1.259272655095799), :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.9891205802321482
 ⋮
 1.2734927993050285
 1.2861329252906988
 1.2798128618325164
 1.2403124655643292
 1.2600626642388575
 1.271912783176925
 1.2687527516872124
 1.2592726571728394
 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.014505893017901422
  0.0321715817650597
 -0.012121212121353335
 -0.04995617878725092
  0.025830258276829287
  0.02428057552981391
  0.02194907813611017
 -0.04982817866116127
 -0.013562386979230262
  0.005499541703913747
  ⋮
 -0.03357314105296417
  0.00992555748455604
 -0.0049140048698729085
 -0.03086419698238386
  0.01592356702271976
  0.009404388586678487
 -0.0024844718376204884
 -0.0074719794709930945
  0.032622333744543965)), 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.