Log-wealth regret against a comparator: private API

PortfolioOptimisers.regret_series — Function
regret_series(pred::PredictionResult)
regret_series(pred::MultiPeriodPredictionResult)

The realised return series and the timestamps a prediction result was scored on, as log_wealth_regret reads them.

The series is the one performance_summary reads: the portfolio returns that predict stored, net of the fee the fold settled, and drifted where a Weight Drift ran. A population of paths gives its first path. A multi-period result gives its stacked returns and timestamps.

Arguments

  • pred: The prediction result.

Returns

  • ret::VecNum: The realised return series.
  • ts::Option{<:AbstractVector}: The timestamps of the rows, or nothing when the fold carried none.

Related

source
PortfolioOptimisers.first_member — Function
first_member(w::VecNum)
first_member(w::VecVecNum)

The one weight vector of a fold, or the first member's under a population.

A population result carries one vector per member, and the path readers of the regret take the first, as regret_series and performance_summary take the first path. A single vector is its own first member.

Arguments

  • w: Weights of a fold, one vector or one per member.

Returns

  • w::VecNum: The vector read.

Related

source
PortfolioOptimisers.fold_target — Function
fold_target(pred::PredictionResult)

The weights a fold started from, and, through fold_held, the weights it held after its last observation, on the fold's own asset names.

On a fold with no Held Weights record, fold_target is the fold's target. On a fold with one, it is the record's w0, the start that held_start_weights names: the fold's own target when it solved, and the weights it was handed when it failed. fold_held is the target on a fold with no record, because a fold without a drift holds its target to the end. On a fold with a record it is the record's w, the drifted holding that the next fold trades from.

Both read the first member of a population. Both are viewed at the fold's Investable Mask, so they sit on pred.rd.nx, the names of the fold's own universe, which stacked_fold_weights embeds by.

Arguments

  • pred: One fold's prediction result.

Returns

  • w::VecNum: The weights, one entry per name in pred.rd.nx.

Related

source
PortfolioOptimisers.stacked_fold_weights — Function
stacked_fold_weights(pred::MultiPeriodPredictionResult, read) -> MatNum

One weight vector per fold, read by read and embedded by asset name on the union of the folds' universes.

The folds of a multi-period result need not share a universe. A Pipeline with an Asset Selector fits each fold on the assets it selected, so the fold of a top-1 selector carries one name and one weight. A path over the folds is stated on one axis, so each fold's weights go into the columns of its own names on the union axis, and every other entry of its row is zero.

Algorithm

  1. Read each fold's weights through read, giving ws.
  2. Take the union of the folds' rd.nx in order of first appearance, giving names, and map each name to its column, giving pos.
  3. Promote the element types of ws, giving Tf, and allocate W, a folds × assets matrix of zeros of type Tf.
  4. For each fold i, check the length of ws[i] against its rd.nx, and write each weight into row i of W, at the column that pos gives its name.

Arguments

Validation

  • Every fold's weights have one entry per name of its rd.nx. A DimensionMismatch is thrown otherwise, because the embedding would misplace them.

Returns

  • W::MatNum: folds × assets weights on the union universe, one row per fold in split order.

Related

source
PortfolioOptimisers.comparator_path_length — Function
comparator_path_length(b::PredictionResult, ::Type{Tf})
comparator_path_length(b::MultiPeriodPredictionResult, ::Type{Tf})

The path length of a comparator's targets, as log_wealth_regret reports it.

A multi-period result has one target per fold, so the path is over the folds. At test_size = 1 a fold is a row, and $\boldsymbol{u}_t$ is the comparator's target on row $t$. A single prediction result holds one target and no path, and so does a multi-period result of one fold. Both answer NaN, in the number type Tf of the regret.

Mathematical definition

\[\begin{align} P_T &= \sum_{t=2}^{T} \lVert \boldsymbol{u}_t - \boldsymbol{u}_{t-1} \rVert_2\,. \end{align}\]

Where:

  • $P_T = \sum_{t=2}^{T} \lVert \boldsymbol{u}_t - \boldsymbol{u}_{t-1} \rVert_2$: Path length of a comparator sequence $\boldsymbol{u}_1, \ldots, \boldsymbol{u}_T$ in the feasible set.
  • $\boldsymbol{u}_t$: Comparator's target on fold $t$, embedded by asset name on the union of the folds' universes, with a zero at every asset outside the fold's own universe.
  • $T$: Number of folds.

Algorithm

  1. Read one target per fold through fold_target, and embed the targets on one axis through stacked_fold_weights, giving W, one row per fold.
  2. When W has fewer than two rows, return NaN of type Tf.
  3. Sum the Euclidean norms of the differences of consecutive rows of W.

Arguments

  • b: The comparator's prediction result.
  • Tf: Number type of the answer when the comparator has no path.

Returns

  • P::Number: The path length, or NaN.

Related

source
PortfolioOptimisers.path_norm_epigraph! — Function
path_norm_epigraph!(model::JuMP.Model, t, x, p::Number)

Writes the epigraph $t \geq \lVert \boldsymbol{x} \rVert_p$ of one step of a comparator path into a bare model.

The cone is the norm's own at p = 2, p = 1 and p = Inf. Every other order takes the power-cone form, one row per entry. It is the epigraph that norm_ball_dual_norm_epigraph! writes, without the model registry, which a bare programme does not use.

JuMP formulation

Variables

  • t: read. It is the caller's epigraph variable.
  • r: created at an order other than 1, 2 and Inf, an anonymous vector of one variable per entry of $\boldsymbol{x}$.

Constraints

Every row is registered under no name.

  • At $p = 2$, the second-order cone row $t \geq \lVert \boldsymbol{x} \rVert_2$.
  • At $p = 1$, the norm-one cone row $t \geq \lVert \boldsymbol{x} \rVert_1$.
  • At $p = \infty$, the norm-infinity cone row $t \geq \lVert \boldsymbol{x} \rVert_\infty$.
  • At any other $p$, one power cone row per entry, $r_j^{1/p}\, t^{1 - 1/p} \geq \lvert x_j \rvert$ with $r_j \geq 0$ and $t \geq 0$, and the row $\sum_j r_j - t = 0$.

Where:

  • $t$: Epigraph variable of the step.
  • $\boldsymbol{x}$: Affine expression whose norm is bounded, with entries $x_j$.
  • $p$: Norm order.
  • $r_j$: Auxiliary variable of entry $j$.

The power-cone rows are exact. Each row gives $r_j \geq \lvert x_j \rvert^p / t^{p-1}$, and the sum of the rows gives $t^p \geq \sum_j \lvert x_j \rvert^p$, which is $t \geq \lVert \boldsymbol{x} \rVert_p$.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • t: The epigraph variable.
  • x: The affine expression whose norm is bounded, one entry per asset.
  • p: The norm order, 1 <= p.

Returns

  • nothing.

Related

source
PortfolioOptimisers.row_bound_view — Function
row_bound_view(b::Number, m::AbstractVector{Bool})
row_bound_view(b::VecNum, m::AbstractVector{Bool})

One side of a resolved weight bound on the investable assets of a row.

A scalar bound applies to every asset, so it passes through. A vector bound is viewed at the row's mask.

Arguments

  • b: One side of the resolved bound, a number or one entry per asset.
  • m: The row's Investable Mask.

Returns

  • b: The scalar bound, or the view of the vector bound at m.

Related

source
PortfolioOptimisers.path_row_constraints! — Function
path_row_constraints!(model::JuMP.Model, u, z, x, m::AbstractVector{Bool}, wb::WeightBounds)

Writes one row of the budgeted path programme on the row's investable assets.

The budget, the finite bounds and the log wealth of the row read the assets that m keeps. The allocation of every other asset is fixed at zero, because a row with no price relative for an asset cannot hold it.

JuMP formulation

Variables

  • u: read. It is the row's allocations, one per asset. Every entry outside m is fixed at zero with JuMP.fix, which sets the variable's bounds and adds no row.
  • z: read. It is the row's log-wealth variable.

Constraints

Every row is registered under no name, and each reads the assets in $m$ alone.

  • $\sum_{i \in m} u_i = 1$.
  • $u_i - l_i \geq 0$ for each $i \in m$, when an entry of $\boldsymbol{l}$ is finite.
  • $u_i - h_i \leq 0$ for each $i \in m$, when an entry of $\boldsymbol{h}$ is finite.
  • The exponential cone row $\left(z, 1, \sum_{i \in m} x_i u_i\right) \in \mathcal{K}_{\exp}$, which holds when $z \leq \log \sum_{i \in m} x_i u_i$.

Where:

  • $u_i$: Allocation of asset $i$ on the row.
  • $z$: Log-wealth variable of the row.
  • $x_i$: Price relative of asset $i$ on the row, one plus its return.
  • $m$: Set of the assets in the row's Investable Mask.
  • $l_i$, $h_i$: Resolved lower and upper weight bounds of asset $i$, the entries of $\boldsymbol{l}$ and $\boldsymbol{h}$.
  • $\mathcal{K}_{\exp} = \mathrm{cl}\,\{(a, b, c) : b\, e^{a / b} \leq c,\, b > 0\}$: Exponential cone.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • u: The row's allocation variables, one per asset.
  • z: The row's log-wealth variable.
  • x: The row's price relatives, finite where m holds.
  • m: The row's Investable Mask, the Coverage Universe of the one-row window.
  • wb: Weight bounds.

Returns

  • nothing.

Related

source
PortfolioOptimisers.path_budget_constraint! — Function
path_budget_constraint!(model::JuMP.Model, u, L::Number, p::Number)

Writes the path-length budget of the budgeted path programme.

Each step of the path takes one epigraph variable through path_norm_epigraph!, and the sum of these variables is at most L. A path of one row has no step, so the function writes nothing.

JuMP formulation

Variables

  • u: read. It is the T × N allocation variables, one row per period.
  • s: created, an anonymous vector of $T - 1$ step lengths.
  • d: created, an anonymous $(T - 1) \times N$ matrix of steps.

Constraints

Every row is registered under no name.

  • $\boldsymbol{d}_{t-1} = \boldsymbol{u}_t - \boldsymbol{u}_{t-1}$, for $t = 2, \ldots, T$, one row per entry.
  • The rows of path_norm_epigraph! on $s_{t-1} \geq \lVert \boldsymbol{d}_{t-1} \rVert_p$, for $t = 2, \ldots, T$.
  • $\sum_{t=1}^{T-1} s_t \leq L$.

Where:

  • $\boldsymbol{u}_t$: Allocation of period $t$, one entry per asset.
  • $\boldsymbol{d}_t$: Step from period $t$ to period $t + 1$.
  • $s_t$: Length of step $t$ in the norm of order $p$.
  • $L$: Path-length budget.
  • $p$: Norm order.
  • $T$: Number of observations.
  • $N$: Number of assets.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • u: The T × N allocation variables, one row per period.
  • L: The path-length budget.
  • p: The norm order.

Returns

  • nothing.

Related

source
PortfolioOptimisers.row_coverage_mask — Function
row_coverage_mask(rd::ReturnsResult, t::Integer) -> Option{BitVector}

The Coverage Universe of row t of rd alone, through coverage_mask on the one-row view.

Arguments

  • rd: The returns result to use.
  • t: The row.

Validation

  • The row has at least one asset in its Coverage Universe. An IsEmptyError that names the row is thrown otherwise, through row_universe_error. Any other error of coverage_mask passes through as it is.

Returns

  • msk::Option{BitVector}: The row's mask, one entry per asset, or nothing when the row covers every asset.

Related

source
PortfolioOptimisers.path_row_masks — Function
path_row_masks(rd::ReturnsResult) -> Option{BitMatrix}

The Investable Mask of every row of a budgeted path.

Row t is the Coverage Universe of the one-row window t: true at every asset whose return is finite and whose Asset Panel active mask is true on that row. A delisted or unlisted asset is therefore out of the row, as it is out of the fit of a HindsightSplit(; prefix = false) fold.

Algorithm

  1. Start from msk, a rows × assets matrix of true.
  2. For each row t, derive the row's Coverage Universe through row_coverage_mask, giving cm, and write it into row t of msk when it is not nothing.
  3. Return nothing when every entry of msk is true, and msk otherwise.

Arguments

  • rd: The returns result to use.

Validation

  • Every row has at least one asset in its Coverage Universe. An IsEmptyError naming the row is thrown otherwise, because the row's budget of one cannot be met.

Returns

  • msk::Option{BitMatrix}: The masks, rows × assets, or nothing.

Related

source
PortfolioOptimisers.path_row_fold — Function
path_row_fold(res::BudgetedHindsightPathResult, rd::ReturnsResult, t::Integer) -> PredictionResult

One fold of a budgeted path's prediction result: row t's allocation as a NaiveOptimisationResult on the resolved bounds, predicted over row t of rd alone.

A row with a gap carries its Investable Mask, so predict views the row and the weights at the mask and never reads the gap. A row with no gap carries nothing, as a fit on the full universe does.

Related

source