The source files can be found in user_guide/.
Online portfolio selection
Most optimisers met so far fit moments, and those that take a solver, such as MeanRisk, then solve a programme. Most rules of the online portfolio selection family do neither. A rule of the family is one recursion, w_{t+1} = f(w_t, x_t). It takes the price relative x_t = 1 + r_t the market just realised and moves the allocation from it.
OnlinePortfolioSelection is the naive optimiser that runs a rule. In batch it makes one causal pass over the rows. It goes through every row in order from the start allocation, and it returns the portfolio for the period after the last row. Its online updates are the same recursion one row at a time. A walk-forward at test_size = 1 and the batch pass give the same weights.
Many rules carry a guarantee that holds on every price sequence, with no statistical assumption. The wealth of such a rule cannot fall far behind the best constant portfolio chosen in hindsight. That guarantee is a bound, and it is not a return. The follow-the-loser rules carry no such guarantee.
The rules fall into the five groups of Li and Hoi's (2014) survey. Sections 1 to 4 run two benchmarks, three follow-the-winner rules and three follow-the-loser rules, and measure them. Section 5 runs a follow-the-loser rule with a new forecast, section 6 runs the follow-the-winner step in three more geometries, and section 7 runs a pattern-matching rule. Section 8 measures the rules on the reverting market against comparators that change at every row. The page runs no meta-learning rule, and the full roster by group is at the end of the page.
using PortfolioOptimisers, StableRNGs, DataFrames, PrettyTables, Statistics, Dates, Clarabelresfmt = (v, i, j) -> isa(v, AbstractFloat) ? "$(round(v; digits = 3))" : v;1. Two markets, side by side
Both markets are five assets over five hundred periods from one seed. In the mean-reverting market each log price is pulled back to a level, so a rise is followed by a fall. In the trending market the log return of each asset is persistent, so a rise is followed by another rise. Nothing else differs.
function synthetic_market(kind; T = 500, N = 5, seed = 7) rng = StableRNG(seed) P = ones(T + 1, N) lp = zeros(N) for t in 1:T if kind == :trending lp .= 0.90 .* lp .+ 0.008 .* randn(rng, N) P[t + 1, :] .= P[t, :] .* exp.(lp) else lp .= 0.90 .* lp .+ 0.04 .* randn(rng, N) P[t + 1, :] .= exp.(lp) end end days = filter(d -> Dates.dayofweek(d) <= 5, Date(2016, 1, 4):Day(1):(Date(2016, 1, 4) + Day(2 * T + 10)))[1:T] return ReturnsResult(; nx = ["A$i" for i in 1:N], X = P[2:end, :] ./ P[1:(end - 1), :] .- 1, ts = days)endrd_rev = synthetic_market(:reverting)rd_trend = synthetic_market(:trending)N = size(rd_rev.X, 2)5The roster below is two benchmark rules, three rules that follow the winner and three that follow the loser. We run every rule through OnlinePortfolioSelection and the same walk-forward. It takes twenty warm-up rows, then one row per fold under OnlineIndexWalkForward. Each fold adds one row and returns one portfolio. UniversalPortfolio samples its constant portfolios when you construct it. It needs the universe size for that, and a seed fixes the draw.
rules = ["Buy and hold" => BuyAndHold(), "Constant rebalanced" => ConstantRebalancedPortfolio(), "Exponentiated gradient" => ExponentiatedGradient(), "Newton step" => NewtonStep(), "Universal portfolio" => UniversalPortfolio(; N = N, seed = 1), "Passive-aggressive mean reversion" => PassiveAggressiveMeanReversion(), "Moving-average reversion" => MovingAverageReversion(), "Robust median reversion" => RobustMedianReversion()]cv = OnlineIndexWalkForward(20, 1)online(alg, rd) = cross_val_predict(OnlinePortfolioSelection(; alg = alg), rd, cv)wealth(pred) = prod(1 .+ pred.mrd.X)preds_rev = Dict(name => online(alg, rd_rev) for (name, alg) in rules)preds_trend = Dict(name => online(alg, rd_trend) for (name, alg) in rules)pretty_table(DataFrame("Rule" => first.(rules), "Reverting market" => [wealth(preds_rev[n]) for n in first.(rules)], "Trending market" => [wealth(preds_trend[n]) for n in first.(rules)]); formatters = [resfmt], title = "Terminal wealth from a unit start, 480 periods") Terminal wealth from a unit start, 480 periods
┌───────────────────────────────────┬──────────────────┬─────────────────┐
│ Rule │ Reverting market │ Trending market │
│ String │ Float64 │ Float64 │
├───────────────────────────────────┼──────────────────┼─────────────────┤
│ Buy and hold │ 1.064 │ 3.177 │
│ Constant rebalanced │ 1.476 │ 2.863 │
│ Exponentiated gradient │ 1.452 │ 2.88 │
│ Newton step │ 1.475 │ 2.865 │
│ Universal portfolio │ 1.4 │ 2.901 │
│ Passive-aggressive mean reversion │ 6.314 │ 0.001 │
│ Moving-average reversion │ 22.39 │ 0.002 │
│ Robust median reversion │ 13.298 │ 0.001 │
└───────────────────────────────────┴──────────────────┴─────────────────┘The constant rebalanced portfolio, the exponentiated gradient, the Newton step and the universal portfolio sit close together on each market. Each of them moves slowly away from a constant portfolio, and a constant portfolio is what their guarantee is stated against. Buy and hold ends within a factor of them on both markets.
A follow-the-loser rule is a bet on one property of the market. Where prices revert, the rule buys every fall and is paid for it on the next row, and the reversion rules end far above the other rules of the table. Where prices trend, the same rule buys every fall into a further fall, and every reversion rule loses almost all of its start. Nothing in the rule finds out which market it is in, and both results come from the same update.
Run a winner rule and a loser rule side by side on the market at hand, and use the pair as a test of whether that market reverts or trends.
2. The regret table
The guarantee is stated against a comparator chosen in hindsight, and you measure such a comparator in two steps. Fit it on the rows the strategy was scored on, and predict it in sample over those same rows. Three comparators cover the literature. The best constant rebalanced portfolio is BestConstantRebalancedPortfolio, Cover's (1984) fixed point, which needs no solver. It stops at a budget of steps, so it is exact only to the precision of this page's tables. You can compute the same portfolio with MeanRisk under a LogarithmicReturn and a maximum-return objective, on a solver, to the tolerance of the solver. The best stock is a top-one ScoreSelector on log wealth followed by EqualWeighted, through a Pipeline. The uniform constant rebalanced portfolio is a causal member of the family, run through the same walk-forward.
cut(rd, i) = ReturnsResult(; nx = rd.nx, X = rd.X[i, :], ts = rd.ts[i])test_rows(rd) = cut(rd, 21:size(rd.X, 1))best_stock = Pipeline(; steps = (ScoreSelector(; score = MeanReturn(; flag = true), rule = RankRule(; best = 1)), EqualWeighted()))function comparators(rd, preds) rdt = test_rows(rd) return ["Best CRP" => predict(optimise(BestConstantRebalancedPortfolio(), rdt), rdt), "Best stock" => predict(fit(best_stock, rdt), rdt), "Uniform CRP" => preds["Constant rebalanced"]]endfunction regret_table(rd, preds) df = DataFrame("Rule" => first.(rules)) for (name, comp) in comparators(rd, preds) df[!, name] = [log_wealth_regret(preds[n], comp).regret for n in first.(rules)] end return dfendpretty_table(regret_table(rd_rev, preds_rev); formatters = [resfmt], title = "Log-wealth regret on the reverting market, comparator minus rule") Log-wealth regret on the reverting market, comparator minus rule
┌───────────────────────────────────┬──────────┬────────────┬─────────────┐
│ Rule │ Best CRP │ Best stock │ Uniform CRP │
│ String │ Float64 │ Float64 │ Float64 │
├───────────────────────────────────┼──────────┼────────────┼─────────────┤
│ Buy and hold │ 0.407 │ 0.17 │ 0.327 │
│ Constant rebalanced │ 0.079 │ -0.157 │ 0.0 │
│ Exponentiated gradient │ 0.096 │ -0.14 │ 0.016 │
│ Newton step │ 0.08 │ -0.156 │ 0.0 │
│ Universal portfolio │ 0.132 │ -0.104 │ 0.053 │
│ Passive-aggressive mean reversion │ -1.374 │ -1.61 │ -1.453 │
│ Moving-average reversion │ -2.64 │ -2.876 │ -2.719 │
│ Robust median reversion │ -2.119 │ -2.355 │ -2.198 │
└───────────────────────────────────┴──────────┴────────────┴─────────────┘pretty_table(regret_table(rd_trend, preds_trend); formatters = [resfmt], title = "Log-wealth regret on the trending market, comparator minus rule") Log-wealth regret on the trending market, comparator minus rule
┌───────────────────────────────────┬──────────┬────────────┬─────────────┐
│ Rule │ Best CRP │ Best stock │ Uniform CRP │
│ String │ Float64 │ Float64 │ Float64 │
├───────────────────────────────────┼──────────┼────────────┼─────────────┤
│ Buy and hold │ 0.603 │ 0.603 │ -0.104 │
│ Constant rebalanced │ 0.707 │ 0.707 │ 0.0 │
│ Exponentiated gradient │ 0.701 │ 0.701 │ -0.006 │
│ Newton step │ 0.706 │ 0.706 │ -0.001 │
│ Universal portfolio │ 0.694 │ 0.694 │ -0.013 │
│ Passive-aggressive mean reversion │ 8.532 │ 8.532 │ 7.825 │
│ Moving-average reversion │ 8.196 │ 8.196 │ 7.489 │
│ Robust median reversion │ 8.377 │ 8.377 │ 7.67 │
└───────────────────────────────────┴──────────┴────────────┴─────────────┘log_wealth_regret returns the log terminal wealth of the comparator less the log terminal wealth of the strategy. A positive entry means the comparator won. A negative entry is expected and is not a defect. The bound says that a rule cannot fall far behind the best constant portfolio, and it says nothing about the rule pulling ahead of it. A causal rule that follows the structure of the market does pull ahead, as every reversion rule on the reverting market does against the best constant portfolio.
The best stock in hindsight is a weaker comparator than the best constant rebalanced portfolio, so the Best stock column never stands above the Best CRP column. On the trending market the two columns are equal, because the best constant portfolio there holds one asset.
log_wealth_regret also returns a per-row difference series and a Newey-West test of it. The comparator saw those rows and was chosen to win on this one sequence, so its p is optimistic. Read it as a rank, and not as a probability.
The literature bounds the row of the universal portfolio. The UniversalPortfolio docstring states both bounds and attributes them. Cover's (1991) (N − 1) log(T + 1) bounds the exact integral, and it is 24.7 at five assets and 480 periods. log n_experts bounds the sampled mixture the library runs against the best sampled expert, and it is 7.6 at the default two thousand experts. The row of the universal portfolio sits far inside both, as every row of the winner half does.
3. The batch pass and the held path
At test_size = 1 the walk-forward and the batch pass give the same weights, and the target of the fold after rows 1:t equals optimise(opt, rd[1:t]). At test_size = k a fold adds k rows one at a time and returns one portfolio. The walk-forward then holds that one portfolio for k periods, where the pass would have rebalanced on every row. If you ask for a weight drift, the book the fund holds drifts with the prices over the block as well, and DriftedWeights hands that drifted book to the fee of the next fold, in place of the target. The target is still the target of the pass. The wealth is not the wealth of the pass.
opt = OnlinePortfolioSelection(; alg = PassiveAggressiveMeanReversion())cv5 = OnlineIndexWalkForward(20, 5)cv5d = OnlineIndexWalkForward(20, 5; wd = SelfFinancingDrift(), pws = DriftedWeights())every_row = preds_rev["Passive-aggressive mean reversion"]block = cross_val_predict(opt, rd_rev, cv5)drifted = cross_val_predict(opt, rd_rev, cv5d)k = 46t_end = last(split(cv5, rd_rev).train_idx[k])(; target_identity = block.pred[k].res.w == optimise(opt, cut(rd_rev, 1:t_end)).w, held_book_gap = maximum(abs, drifted.pred[k].hw.w - drifted.pred[k].res.w), wealth = (; every_row = wealth(every_row), block = wealth(block), drifted = wealth(drifted)))(target_identity = true, held_book_gap = 0.037507757937777164, wealth = (every_row = 6.3141120492152645, block = 2.143673763848831, drifted = 2.086504007983459))The portfolio the fold returns is the portfolio the pass returns over the same rows. The book the fund holds at the end of the block is not. It has drifted away from the target, and the next update of the rule uses its own target, never the drifted book. The gap in wealth comes from how often the rule rebalances. A reversion rule that rebalances on every row is paid on every reversal. One that rebalances every fifth row is paid on every fifth reversal and ends with less. The drift takes a little more. Neither path is wrong. A rule is designed for the rebalancing frequency its paper ran it at, and test_size is that frequency.
4. Tuning a rate
The rate of a rule is a field, and GridSearchCrossValidation addresses it by path. The score is MeanReturn with flag = true, the mean log return per period, which is the log terminal wealth per period. Ranking candidates on that score ranks them on regret against any one fixed comparator, because the wealth of the comparator is the same constant for every candidate.
grid = GridSearchCrossValidation(["alg.eta" => [0.01, 0.05, 0.2, 0.5]]; cv = cv, r = MeanReturn(; flag = true))tuned = search_cross_validation(OnlinePortfolioSelection(; alg = ExponentiatedGradient()), grid, rd_trend)(; eta = tuned.opt.alg.eta, wealth = wealth(cross_val_predict(tuned.opt, rd_trend, cv)))(eta = 0.5, wealth = 3.0312894393651755)The search returns an OnlinePortfolioSelection, so you run it through the walk-forward like any other. On the trending market the largest rate on offer wins, because the gradient keeps pointing the same way and a faster step follows it sooner. The tuned rule ends a little above the exponentiated gradient at its default rate in the first table.
5. One update, two forecasts
A follow-the-loser rule has two parts. It forecasts the next price relative, and it updates the allocation toward that forecast. MovingAverageReversion is ForecastReversion over PriceLevelExpectedReturns with a moving average. Its forecast is the ratio of the average price to the last price, which is below one for an asset that just rose. The same update takes any expected-returns estimator. PriorExpectedReturns takes one from a prior, here an EmpiricalPrior whose mean weights the recent rows most. The forecast for the next row is then the average return of about the last ten rows, carried forward.
recent = PriorExpectedReturns(; pe = EmpiricalPrior(; me = ExpWeightedExpectedReturns(; decay = 0.9, min_obs = 1)))forecasts = ["Moving average" => MovingAverageReversion(), "Recent mean" => ForecastReversion(; me = recent)]pretty_table(DataFrame("Forecast" => first.(forecasts), "Reverting market" => [wealth(online(a, rd_rev)) for a in last.(forecasts)], "Trending market" => [wealth(online(a, rd_trend)) for a in last.(forecasts)]); formatters = [resfmt], title = "Terminal wealth by forecast") Terminal wealth by forecast
┌────────────────┬──────────────────┬─────────────────┐
│ Forecast │ Reverting market │ Trending market │
│ String │ Float64 │ Float64 │
├────────────────┼──────────────────┼─────────────────┤
│ Moving average │ 22.39 │ 0.002 │
│ Recent mean │ 0.016 │ 1189.068 │
└────────────────┴──────────────────┴─────────────────┘The two rows invert each other. The update is the same in both, and only the forecast differs. A moving-average forecast says that a rise reverts. A recent-mean forecast says that a rise continues, and the rule that uses it gains on the trending market and loses on the reverting one. The prior route is the general one, because the expected return of any prior is a forecast, a factor model's or a view-tilted one.
6. Four geometries of one step
MirrorDescent is one first-order step, and proj sets the geometry the step and its projection are taken in. The exponentiated gradient is a MirrorDescent in the entropic geometry, and GradientProjection is one in the Euclidean geometry. LogBarrierProjection lies beyond the entropic geometry, on the side away from the Euclidean one, and TsallisProjection lies between the entropic geometry and the log barrier.
geometries = ["Entropic" => ExponentiatedGradient(), "Tsallis" => MirrorDescent(; proj = TsallisProjection()), "Log barrier" => MirrorDescent(; proj = LogBarrierProjection()), "Euclidean" => GradientProjection()]pretty_table(DataFrame("Geometry" => first.(geometries), "Reverting market" => [wealth(online(a, rd_rev)) for a in last.(geometries)], "Trending market" => [wealth(online(a, rd_trend)) for a in last.(geometries)]); formatters = [resfmt], title = "Terminal wealth by geometry") Terminal wealth by geometry
┌─────────────┬──────────────────┬─────────────────┐
│ Geometry │ Reverting market │ Trending market │
│ String │ Float64 │ Float64 │
├─────────────┼──────────────────┼─────────────────┤
│ Entropic │ 1.452 │ 2.88 │
│ Tsallis │ 1.465 │ 2.871 │
│ Log barrier │ 1.471 │ 2.867 │
│ Euclidean │ 1.36 │ 2.95 │
└─────────────┴──────────────────┴─────────────────┘The three interior geometries land close together on both markets. Each of them is a slow step away from the uniform portfolio, and the geometry only changes how the step is measured. The Euclidean row sits below them on the reverting market, because one eta is not one step size in two geometries. An entropic step scales the gradient by the weight it moves, and a Euclidean step does not.
Choose the geometry for the property you need. A multiplicative step never leaves the interior of the simplex, and a barrier step cannot leave it, so a rule that must hold every asset at all times takes one of those two. A Euclidean step can reach a corner, and the projection clips it back onto the boundary of the set in one move.
7. A leader over a matched sample
The pattern-matching group is FollowTheLeader over a selector of past rows. The rule selects the past rows whose preceding window resembles the rows just seen, and it plays the best constant portfolio over the selected rows alone. NearestNeighbourMatch selects the rows whose two-row history is nearest to the last two rows.
matched = FollowTheLeader(; sel = NearestNeighbourMatch(; window = 2))(; reverting = wealth(online(matched, rd_rev)), trending = wealth(online(matched, rd_trend)))(reverting = 2.851843718406078, trending = 4396.336248288981)It is the one rule on the page that ends well ahead of the constant rebalanced portfolio on both markets, and the one rule that selects past rows by how closely their history matches the last two rows. On the reverting market the rows that followed a rise were falls. On the trending market they were further rises. The leader over those rows is the opposite portfolio in the two cases. The trending market here has a persistence far stronger than that of any real market.
You pay for it with one programme per row, a fixed point without a solver here, and a solve under a ProgrammeAllocationSet. You also wait for history. The selected sample is empty until enough rows have accrued, and the rule holds the uniform portfolio over those rows.
8. Dynamic regret
Section 2 measured every rule against one portfolio chosen in hindsight. A sequence of portfolios is the stronger comparator. HindsightSplit is a walk-forward whose fold t trains on the rows through t and tests on row t, so an estimator run through it is a comparator refitted at every row. log_wealth_regret against its result is dynamic regret, and it reports the path length of the comparator beside it. Under prefix = true the best constant portfolio is refitted at each row on the rows up to and including that row, and is called be-the-leader. Under prefix = false the best stock becomes the best stock of each row.
rdt = test_rows(rd_rev)be_the_leader = cross_val_predict(BestConstantRebalancedPortfolio(), rdt, HindsightSplit())per_row_stock = cross_val_predict(best_stock, rdt, HindsightSplit(; prefix = false))dynamic = DataFrame("Rule" => first.(rules))for (name, comp) in ["Be the leader" => be_the_leader, "Best stock per row" => per_row_stock] dynamic[!, name] = [log_wealth_regret(preds_rev[n], comp).regret for n in first.(rules)]endpretty_table(dynamic; formatters = [resfmt], title = "Dynamic regret on the reverting market, comparator minus rule") Dynamic regret on the reverting market, comparator minus rule
┌───────────────────────────────────┬───────────────┬────────────────────┐
│ Rule │ Be the leader │ Best stock per row │
│ String │ Float64 │ Float64 │
├───────────────────────────────────┼───────────────┼────────────────────┤
│ Buy and hold │ 2.865 │ 22.98 │
│ Constant rebalanced │ 2.538 │ 22.653 │
│ Exponentiated gradient │ 2.554 │ 22.669 │
│ Newton step │ 2.538 │ 22.654 │
│ Universal portfolio │ 2.591 │ 22.706 │
│ Passive-aggressive mean reversion │ 1.084 │ 21.2 │
│ Moving-average reversion │ -0.181 │ 19.934 │
│ Robust median reversion │ 0.34 │ 20.455 │
└───────────────────────────────────┴───────────────┴────────────────────┘(; be_the_leader = log_wealth_regret(preds_rev["Constant rebalanced"], be_the_leader).path_length, best_stock_per_row = log_wealth_regret(preds_rev["Constant rebalanced"], per_row_stock).path_length)(be_the_leader = 73.01720468040041, best_stock_per_row = 576.9991334482262)Be-the-leader is fitted on each row before it is scored on it. It beats every winner rule by far more than the fixed leader of section 2 did. The path lengths above show how far each comparator moves, and the moving-average reversion still ends ahead of be-the-leader. The best stock of each row holds one asset and jumps on nearly every row. Its path is far longer, and no causal rule comes near it.
That is how you read dynamic regret. The bound a rule carries grows with the path length of the comparator, and no rule tracks a comparator that moves on every row. The path_length field reports the path the comparator walked, so you can state the bound the rule is held to.
Neither of those two comparators is the one the definition of dynamic regret names, which is the best sequence under a budget on the path length. Be-the-leader is one sequence at its own path length, and the best stock of each row is the sequence no budget binds. BudgetedHindsightPath solves for the sequence itself, as one programme over the rows. It maximises the log wealth of a path that sits on the simplex at every row, under a budget on the summed length of its steps. It is measured as the best constant portfolio of section 2 is. It is fitted on the rows it is scored on and predicted over them, one fold per row, and log_wealth_regret takes it as it takes any other comparator. At the path length be-the-leader walked, the two are different paths.
slv = Solver(; name = :clarabel, solver = Clarabel.Optimizer, settings = Dict("verbose" => false))crp = preds_rev["Constant rebalanced"]L = log_wealth_regret(crp, be_the_leader).path_lengthbudgeted = predict(optimise(BudgetedHindsightPath(; L = L, slv = slv), rdt), rdt)budgeted_reg = log_wealth_regret(crp, budgeted)(; be_the_leader = log_wealth_regret(crp, be_the_leader).regret, budgeted = budgeted_reg.regret, path_length = budgeted_reg.path_length)(be_the_leader = 2.537877572067358, budgeted = 10.13459536149188, path_length = 73.01720462701616)The budget binds, so the path length equals the one be-the-leader walked. The regret against the best sequence of that length is far larger than the regret against be-the-leader. The steps of the leader are the drift of a running average, and the same length of path, spent where a switch pays the most, buys much more log wealth. The BudgetedHindsightPath docstring states the two ends of the budget. At the path length of the best stock of each row or above it, the budget is slack and the path is the best stock of each row. At a budget of zero the path cannot move, and it is the fixed leader of section 2, the best constant rebalanced portfolio. One estimator therefore covers the fixed leader, the best stock of each row, and every budget between them.
The roster by group
Every rule below goes in the alg keyword of OnlinePortfolioSelection, except BestConstantRebalancedPortfolio, which is an optimiser of its own. A rate, a sample selector and an expert weighting go inside a rule. The groups are Li and Hoi's (2014), and the capability catalogue lists the same roster.
- Benchmarks.
BuyAndHold,ConstantRebalancedPortfolio, and, in hindsight,BestConstantRebalancedPortfolio. - Follow the winner.
MirrorDescentand its constructorsExponentiatedGradient,GradientProjection,EGE,EGR,EGA,MAEG,AEG;NewtonStep;AdaptiveSubgradient;OptimisticStep;ExpectationMaximisation;UniversalPortfolio;AggregatingExponentialGradient. A rate may change over time:InverseSquareRootRate,DoublingTrickRate,SelfConfidentRate,HintResidualRate,WindowedBestRate. - Follow the loser.
PassiveAggressiveMeanReversion;ForecastReversionand its constructorsMovingAverageReversion,ExponentialMovingAverageReversion,RobustMedianReversion,ReweightedPriceRelativeTracking,GaussianWeightingReversion,LocalAdaptiveLearning;ForecastTrackingwithPeakPriceTracking,AdaptiveInputCompositeTrend,TrendPromotePriceTracking;KernelTrendTracking;TransactionCostOptimisation;ShortTermSparsePortfolio;ConfidenceWeightedMeanReversion;AntiCorrelation. - Pattern matching.
FollowTheLeaderover a sample selector:Prefix,LastRows,HistogramMatch,KernelMatch,NearestNeighbourMatch,CorrelationMatch,ClusterMatch; and the solved rulesShortTermLossControlPortfolio,LowDimensionEnsemblePortfolio. - Meta-learning.
ExpertMixtureover any experts, withAder,Sword,SwitchingPortfolioand the weightingsAggregatingAlgorithm,TopK,WeakAggregatingAlgorithm,SwitchingWeighting;FollowTheLeadingHistory.
A rule that reads a forecast takes it from an expected-returns estimator. That is PriceLevelExpectedReturns over a price-level statistic, or the expected return of any prior through PriorExpectedReturns. Every step is projected onto the allocation set of the optimiser, in the geometry of the rule. The default BoundedAllocationSet is the simplex and has a closed form, and a ProgrammeAllocationSet takes the full constraint vocabulary through a solver. You evaluate a run with log_wealth_regret, with the HindsightSplit that refits any estimator at every row, with BudgetedHindsightPath, the best comparator sequence under a budget on the path length, and with performance_summary and a benchmark.
Where to go next
- The online portfolio selection example runs the roster on real prices in a walk-forward with a turnover fee, with the search, the regret table, the weight path of a rule and its discrete allocation.
- The online walk-forward covers the online scheme every run on this page goes through.
- Validation and tuning covers the walk-forward and the search.
- Optimisers covers the naive optimisers, the family
OnlinePortfolioSelectionbelongs to.
This page was generated using Literate.jl.