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.