Price-level expected returns

PriceLevelExpectedReturns forecasts the next return of each asset from its recent price levels. Many online portfolio selection rules read this forecast, and any field that takes an expected returns estimator, me, can hold it. It sets the price level of the last observation to one and rebuilds the earlier levels from the returns. The expected return is then a statistic of those levels, such as a moving average, divided by the last level, minus one.

A window statistic reads the last window - 1 returns, and it uses fewer rows at the start of the data. A recursive statistic updates exactly with each new observation. Add observations with partial_fit!, and read the forecast with mean(me). It can also keep the last few price relatives next to its running value. A recursive statistic reads the active mask. It restarts an asset that the mask turns off, so a relisted asset starts from nothing, and it returns NaN for an asset that has no price level yet.

PortfolioOptimisers.AbstractPriceLevelStatistic — Type
abstract type AbstractPriceLevelStatistic <: AbstractExpectedReturnsAlgorithm

Abstract supertype for the statistics of a window of price levels that PriceLevelExpectedReturns turns into an expected return.

Every statistic reads the reconstructed price path. The level of the last observation is one in every asset, and the returns of the window give the earlier levels. The statistic is a vector over the assets, and the expected return is the statistic divided by the last level, minus one.

A statistic that reads each asset's levels alone is homogeneous of degree one in them, so it has the same value on the path as on the prices. The moving averages, the peak, the lagged price, the reweighted relative and the trend switches are of this kind. A statistic that couples the assets changes when each asset is scaled by its own last price. The spatial median couples them through its Euclidean distances, and the kernel trend pattern through its regression. On the path, each of them is the paper's statistic of prices normalised to one at the current period, and its docstring states the difference.

A statistic is windowed when it reads a fixed number of levels. MovingAverage, SpatialMedian, WindowPeak and LaggedPrice are windowed. A statistic is folding when it is an exact recursion over the price relatives, as ExponentialMovingAverage and ReweightedPriceRelative are. A folding statistic carries one vector on a Partial Fit State and reads no rows. Its batch form over a matrix of returns runs the same recursion from the first level, so a fold and a batch over the same rows agree exactly.

A folding statistic can also carry a memory on the same state, the last memory_rows relatives, as KernelTrendPattern does. Its recursion reads the carried vector and the memory together, so a host holds no rows for it. Its batch form fills the memory as it runs from the first row.

Over the first rows a windowed statistic reads the levels it has. With window = 5 and two returns folded, it reads three levels. This truncation is the library's choice. Over their first window rows, the authors' code for the moving-average, median and peak price papers forecasts the last price relative instead. A folding statistic starts from its seed at the first row, and a composite statistic truncates every window it holds.

A folding statistic reads the active mask. partial_fit! folds the active assets of the row alone, and it resets an inactive asset to cold_statistic, so a relisted asset starts cold. The read-out is NaN at an asset that has folded no level since its last reset. The caller reduces the assets, so a statistic that couples them reads the active assets alone and needs no mask of its own.

Interfaces

To implement a new price-level statistic, subtype AbstractPriceLevelStatistic with all its parameters as part of the struct, and implement the following methods:

  • price_level_statistic(alg::AbstractPriceLevelStatistic, P::AbstractMatrix) -> AbstractVector: The statistic of the levels P, levels × assets, whose last row is the current level. A folding statistic runs its recursion over every row of P from the first.
  • window_rows(alg::AbstractPriceLevelStatistic) -> Union{Nothing, Integer}: The number of return rows the batch form reads to reconstruct its levels. It is window - 1 for a statistic over window levels, and nothing for one that reads every row it gets. The default reads alg.window - 1.
  • folds(alg::AbstractPriceLevelStatistic) -> Bool: true when the statistic is an exact recursion over price relatives, which fold_statistic carries. The default is false.
  • fold_statistic(alg::AbstractPriceLevelStatistic, stat, x::AbstractVector) -> AbstractVector: The recursion of a folding statistic. It takes the carried statistic stat in relative terms, nothing before the first row, and the price relative x of the row. It returns the statistic after the row as a new vector.
  • cold_statistic(alg::AbstractPriceLevelStatistic, x::AbstractVector) -> AbstractVector: The carried statistic of a folding statistic before its first row, in the units the recursion reads. It is the value for which the recursion returns its own seed. The default is one in every asset.
  • memory_rows(alg::AbstractPriceLevelStatistic) -> Integer: The number of relatives a folding statistic carries as its memory. The default is 0, for a recursion that reads the carried vector alone. A statistic with a memory implements the four-argument fold_statistic(alg, stat, hist, x) instead. There hist holds the last memory_rows(alg) relatives with the row's own last, or fewer over the first rows.

Arguments

  • alg: The concrete statistic.
  • P: The reconstructed price levels of the window, levels × assets, the last row being ones.
  • stat: The carried statistic, in units of the last level, or nothing.
  • hist: The carried memory of relatives, rows × assets, the last row being x.
  • x: The price relative of one row, 1 .+ r.

Returns

  • stat::AbstractVector: The statistic per asset, assets × 1.
  • rows::Union{Nothing, Integer}: The number of return rows the batch form reads.
  • folds::Bool: Whether the statistic folds.
  • memory::Integer: The number of relatives the memory holds.

Related

source
PortfolioOptimisers.MovingAverage — Type
struct MovingAverage{T1<:Integer} <: AbstractPriceLevelStatistic

Averages the last window price levels, which is the forecast of the moving-average reversion of Li and Hoi (2012).

The journal version of the paper, Li, Hoi, Sahoo and Liu (2015), allows window >= 2 and runs its experiments at window = 5, the default.

Mathematical definition

\[\begin{align} \hat{\boldsymbol{x}}_{t+1} &= \frac{1}{w} \sum_{k = 0}^{w - 1} \boldsymbol{p}_{t-k} \oslash \boldsymbol{p}_{t}\,. \end{align}\]

Where:

  • $\hat{\boldsymbol{x}}_{t+1}$: Price Relative Forecast for period $t + 1$, made after the row of period $t$.
  • $\boldsymbol{p}_t$: Price level vector of period $t$, one entry per asset.
  • $w$: Price-level window, the number of price levels a statistic reads, the current one included.

Fields

  • window: The number of price levels the statistic reads, the current one included; window - 1 returns reconstruct them.

Constructors

MovingAverage(; window::Integer = 5) -> MovingAverage

Keywords correspond to the struct's fields.

Validation

  • window >= 2. A DomainError is thrown otherwise: one level is the current price, and its ratio to itself forecasts nothing.

Examples

julia> MovingAverage()MovingAverage  window ┴ Int64: 5

Related

References

  • [48] B. Li and S. C. H. Hoi. On-line portfolio selection with moving average reversion. In: Proceedings of the 29th International Conference on Machine Learning (2012).
  • [49] B. Li, S. C. H. Hoi, D. Sahoo and Z.-Y. Liu. Moving average reversion strategy for on-line portfolio selection. Artificial Intelligence 222, 104–123 (2015). Equation (1).
source
PortfolioOptimisers.ExponentialMovingAverage — Type
struct ExponentialMovingAverage{T1<:Real} <: AbstractPriceLevelStatistic

Averages the price levels with weights that fall exponentially with age, which is the forecast of the second form of the moving-average reversion of Li, Hoi, Sahoo and Liu (2015).

It is a folding statistic. The recursion is exact in relative terms, so the statistic reads no rows and carries one vector. The paper admits $\alpha \in (0, 1)$ and states no default. Its sensitivity study finds high wealth over a wide range of $\alpha$ and poor wealth at the two endpoints. The default alpha = 0.5 is the value of the authors' toolbox example. The library also admits alpha = 1, where the forecast is one in every asset.

Mathematical definition

\[\begin{align} \mathrm{MA}_{t} &= \alpha \boldsymbol{p}_{t} + (1 - \alpha) \mathrm{MA}_{t-1}\,,\quad \mathrm{MA}_{0} = \boldsymbol{p}_{0}\,,\\ \hat{\boldsymbol{x}}_{t+1} &= \mathrm{MA}_{t} \oslash \boldsymbol{p}_{t} = \alpha \boldsymbol{1} + (1 - \alpha)\, \hat{\boldsymbol{x}}_{t} \oslash \boldsymbol{x}_{t}\,,\quad \hat{\boldsymbol{x}}_{1} = \boldsymbol{1}\,. \end{align}\]

Where:

  • $\mathrm{MA}_{t}$: The exponential moving average of the levels up to period $t$.
  • $\boldsymbol{p}_t$: Price level vector of period $t$, one entry per asset. The first level is $\boldsymbol{p}_{0}$.
  • $\boldsymbol{x}_t$: Price relative vector of period $t$, each asset's price over its price one period earlier.
  • $\alpha$: Smoothing weight on the current level, in $(0, 1]$. A smaller weight gives a longer memory.
  • $\hat{\boldsymbol{x}}_{t+1}$: Price Relative Forecast for period $t + 1$, made after the row of period $t$.

The paper writes the first price as $\boldsymbol{p}_{1}$ and seeds the average there. The seed above is the same seed with the first level indexed zero, so the forecast after one level is one.

Fields

  • alpha: The smoothing weight on the current level, 0 < alpha <= 1. A smaller weight gives a longer memory.

Constructors

ExponentialMovingAverage(; alpha::Real = 0.5) -> ExponentialMovingAverage

Keywords correspond to the struct's fields.

Validation

  • 0 < alpha <= 1. A DomainError is thrown otherwise.

Examples

julia> ExponentialMovingAverage()ExponentialMovingAverage  alpha ┴ Float64: 0.5

Related

References

  • [49] B. Li, S. C. H. Hoi, D. Sahoo and Z.-Y. Liu. Moving average reversion strategy for on-line portfolio selection. Artificial Intelligence 222, 104–123 (2015). Equation (2).
source
PortfolioOptimisers.SpatialMedian — Type
struct SpatialMedian{T1<:Integer, T2<:Integer, T3<:Real} <: AbstractPriceLevelStatistic

Takes the spatial ($L_1$) median of the last window price levels, which is the forecast of the robust median reversion of Huang, Zhou, Li, Hoi and Zhou (2016).

spatial_median finds the median with the modified Weiszfeld iteration of Vardi and Zhang (2000), after it tests each level as the median.

The distances couple the assets, so the median changes when each asset is scaled by its own price. The library reads it on the reconstructed path, with every asset's last level at one. The paper takes the median of the raw prices, and the authors' code takes it of prices rebased to one on the first day of the series. On the test fixture, the library's forecast differs from the rebased one by up to 0.003. It differs from the raw one by up to 0.08 when one asset trades at a hundred times the price of the others. The normalised median is scale-free, so no asset dominates the distances through its quotation.

The paper stops its iteration when $\lVert \boldsymbol{\mu}_{i-1} - \boldsymbol{\mu}_{i} \rVert_1 \leq \tau \lVert \boldsymbol{\mu}_{i} \rVert_1$, and it gives no value for its maximum number of iterations. The authors' code stops at $\tau = 10^{-9}$ or after 200 iterations. The library stops when the Euclidean change of the iterate is below tol, or after iters iterations. On the test fixture the defaults leave the forecast within $3 \times 10^{-8}$ of the minimiser, as close as the authors' code.

When the median lies very near a level but not on it, the Weiszfeld iteration converges slowly. The cap of iters can then stop it with the forecast about $10^{-4}$ from the minimiser, while the sum of distances is within $10^{-6}$ of its minimum. The steps are then small, so tol stops it early too. Raise iters and lower tol together where that difference matters.

Mathematical definition

\[\begin{align} \boldsymbol{m}_{t} &= \underset{\boldsymbol{y}}{\arg\min} \sum_{k = 0}^{w - 1} \lVert \boldsymbol{p}_{t-k} - \boldsymbol{y} \rVert\,,\\ \hat{\boldsymbol{x}}_{t+1} &= \boldsymbol{m}_{t} \oslash \boldsymbol{p}_{t}\,. \end{align}\]

Where:

  • $\boldsymbol{m}_{t}$: The spatial median of the window that ends at period $t$.
  • $\boldsymbol{p}_t$: Price level vector of period $t$, one entry per asset.
  • $w$: Price-level window, the number of price levels a statistic reads, the current one included.
  • $\hat{\boldsymbol{x}}_{t+1}$: Price Relative Forecast for period $t + 1$, made after the row of period $t$.

Fields

  • window: The number of price levels the statistic reads, the current one included; window - 1 returns reconstruct them.
  • iters: Maximum number of Weiszfeld iterations.
  • tol: Convergence tolerance on the Euclidean distance between two successive iterates.

Constructors

SpatialMedian(; window::Integer = 5, iters::Integer = 100, tol::Real = 1e-8) -> SpatialMedian

Keywords correspond to the struct's fields.

Validation

  • window >= 2. A DomainError is thrown otherwise.
  • iters >= 1. A DomainError is thrown otherwise.
  • tol > 0. A DomainError is thrown otherwise.

Examples

julia> SpatialMedian()SpatialMedian  window ┼ Int64: 5   iters ┼ Int64: 100     tol ┴ Float64: 1.0e-8

Related

References

  • [50] D. Huang, J. Zhou, B. Li, S. C. H. Hoi and S. Zhou. Robust median reversion strategy for online portfolio selection. IEEE Transactions on Knowledge and Data Engineering 28, 2480–2493 (2016). Equations (2) and (5), Algorithm 1.
  • [51] Y. Vardi and C.-H. Zhang. The multivariate L1-median and associated data depth. Proceedings of the National Academy of Sciences 97, 1423–1426 (2000).
source
PortfolioOptimisers.WindowPeak — Type
struct WindowPeak{T1<:Integer} <: AbstractPriceLevelStatistic

Takes the highest of the last window price levels, which is the forecast of the peak price tracking of Lai, Dai, Ren and Huang (2018).

The peak price tracking paper is not open access. Li, Luo and Xu (2023, eq. 5) restate its peak, and the short-term sparse portfolio of Lai, Yang, Fang and Wu (2018, eq. 10) uses the same peak. That portfolio does not read the ratio itself. It reads $1.1 \log \hat{x}_{t+1, i} + 1$ (eq. 11), a transform that ShortTermSparsePortfolio applies.

Mathematical definition

\[\begin{align} \hat{\boldsymbol{x}}_{t+1} &= \max_{0 \leq k < w} \boldsymbol{p}_{t-k} \oslash \boldsymbol{p}_{t} \geq \boldsymbol{1}\,. \end{align}\]

Where:

  • $\hat{\boldsymbol{x}}_{t+1}$: Price Relative Forecast for period $t + 1$, made after the row of period $t$.
  • $\boldsymbol{p}_t$: Price level vector of period $t$, one entry per asset.
  • $w$: Price-level window, the number of price levels a statistic reads, the current one included.

The maximum is taken per asset. The peak is never below the last level, so the forecast is at least one in every asset, and it is exactly one for an asset at its own window peak.

Fields

  • window: The number of price levels the statistic reads, the current one included; window - 1 returns reconstruct them.

Constructors

WindowPeak(; window::Integer = 5) -> WindowPeak

Keywords correspond to the struct's fields.

Validation

  • window >= 2. A DomainError is thrown otherwise.

Examples

julia> WindowPeak()WindowPeak  window ┴ Int64: 5

Related

References

  • [52] Z.-R. Lai, D.-Q. Dai, C.-X. Ren and K.-K. Huang. A peak price tracking-based learning system for portfolio selection. IEEE Transactions on Neural Networks and Learning Systems 29, 2823–2832 (2018).
  • [53] Z.-R. Lai, P.-Y. Yang, L. Fang and X. Wu. Short-term sparse portfolio optimization based on alternating direction method of multipliers. Journal of Machine Learning Research 19, 1–28 (2018). Equations (10) and (11).
  • [54] B. Li, J. Luo and H. Xu. A portfolio selection strategy based on the peak price involving randomness. IEEE Access 11, 52066–52074 (2023). Equation (5).
source
PortfolioOptimisers.LaggedPrice — Type
struct LaggedPrice{T1<:Integer} <: AbstractPriceLevelStatistic

Takes the price level lag observations ago, which is the forecast of the first form of the transaction-cost optimisation of Li, Wang, Huang and Hoi (2018).

The forecast bets that the moves of the last lag periods revert. The paper's first form reads lag = 1 (Algorithm 2). At lag = 0 the forecast is one in every asset, which is the hold branch of a switched statistic. Over fewer than lag rows the statistic reads the first level it has.

Mathematical definition

\[\begin{align} \hat{\boldsymbol{x}}_{t+1} &= \boldsymbol{p}_{t-\ell} \oslash \boldsymbol{p}_{t}\,. \end{align}\]

Where:

  • $\hat{\boldsymbol{x}}_{t+1}$: Price Relative Forecast for period $t + 1$, made after the row of period $t$.
  • $\boldsymbol{p}_t$: Price level vector of period $t$, one entry per asset.
  • $\ell$: The lag, the number of periods between the forecast level and the current one.

At $\ell = 1$ the forecast is $\boldsymbol{1} \oslash \boldsymbol{x}_{t}$, the reciprocal of the last price relative $\boldsymbol{x}_{t}$.

Fields

  • lag: The number of observations between the forecast level and the current one, 0 for the current level.

Constructors

LaggedPrice(; lag::Integer = 1) -> LaggedPrice

Keywords correspond to the struct's fields.

Validation

  • lag >= 0. A DomainError is thrown otherwise.

Examples

julia> LaggedPrice()LaggedPrice  lag ┴ Int64: 1

Related

References

  • [55] B. Li, J. Wang, D. Huang and S. C. H. Hoi. Transaction cost optimization for online portfolio selection. Quantitative Finance 18, 1411–1424 (2018). Algorithm 2.
source
PortfolioOptimisers.ReweightedPriceRelative — Type
struct ReweightedPriceRelative{T1<:Real} <: AbstractPriceLevelStatistic

Averages the price levels with a weight per asset that depends on the data, which is the reweighted price relative of Lai, Yang, Fang and Wu (2020).

It is a folding statistic. The paper is not open access. Li, Luo and Xu (2023, eqs. 8 and 11) restate the recursion, and the MATLAB code that the authors publish runs the same recursion. The seed $\hat{\boldsymbol{\varphi}}_{1} = \boldsymbol{x}_{1}$ is that of the restatement, so the forecast after the first row is one. The authors' code seeds the forecast at one instead. On the test fixture at theta = 0.8, the two forecasts differ by 0.024 after the first row and by less than $10^{-4}$ after ten rows. The default theta = 0.8 is the value of the authors' code. The restating paper sets 0.7 for its own rule.

Mathematical definition

\[\begin{align} \boldsymbol{\gamma}_{t+1} &= \theta \boldsymbol{x}_{t} \oslash (\theta \boldsymbol{x}_{t} + \hat{\boldsymbol{\varphi}}_{t})\,,\\ \hat{\boldsymbol{\varphi}}_{t+1} &= \boldsymbol{\gamma}_{t+1} + (\boldsymbol{1} - \boldsymbol{\gamma}_{t+1}) \odot \hat{\boldsymbol{\varphi}}_{t} \oslash \boldsymbol{x}_{t}\,,\quad \hat{\boldsymbol{\varphi}}_{1} = \boldsymbol{x}_{1}\,. \end{align}\]

Where:

  • $\boldsymbol{x}_t$: Price relative vector of period $t$, each asset's price over its price one period earlier.
  • $\boldsymbol{\gamma}_{t+1}$: The weight of each asset on its current relative.
  • $\theta$: The reweighting strength. A larger value puts more weight on the current relative.
  • $\hat{\boldsymbol{\varphi}}_{t+1}$: The Price Relative Forecast for period $t + 1$, in the symbol of the restating paper.

ExponentialMovingAverage is the case of a constant weight $\boldsymbol{\gamma}_{t+1} = \alpha \boldsymbol{1}$.

Fields

  • theta: The reweighting strength theta > 0 on the current price relative.

Constructors

ReweightedPriceRelative(; theta::Real = 0.8) -> ReweightedPriceRelative

Keywords correspond to the struct's fields.

Validation

  • theta > 0. A DomainError is thrown otherwise.

Examples

julia> ReweightedPriceRelative()ReweightedPriceRelative  theta ┴ Float64: 0.8

Related

References

  • [56] Z.-R. Lai, P.-Y. Yang, L. Fang and X. Wu. Reweighted price relative tracking system for automatic portfolio optimization. IEEE Transactions on Systems, Man, and Cybernetics: Systems 50, 4349–4361 (2020).
  • [54] B. Li, J. Luo and H. Xu. A portfolio selection strategy based on the peak price involving randomness. IEEE Access 11, 52066–52074 (2023). Equations (8) and (11).
source
PortfolioOptimisers.PriceLevelExpectedReturns — Type
struct PriceLevelExpectedReturns{T1<:AbstractPriceLevelStatistic, T2<:(Union{Nothing, var"#s1800"} where var"#s1800"<:PriceLevelForecastState)} <: AbstractExpectedReturnsEstimator

Forecasts the next return of each asset as the move from the last price level to a statistic of a window of price levels.

The forecast is the Price Relative Forecast of the online portfolio selection family, as an expected-returns estimator that any me field can hold. It is a forecast of the next period's return under the reversion or trend hypothesis of the paper that defines the statistic, not an average of past returns. The estimator reads every statistic on the reconstructed path, so the forecast is a function of the returns alone. AbstractPriceLevelStatistic states where this path changes the paper's statistic.

A windowed statistic reads its last window - 1 returns, and over the first rows it reads the levels it has. A folding statistic, such as ExponentialMovingAverage or ReweightedPriceRelative, is an exact recursion over the price relatives. partial_fit! folds one row into the state that the cache field carries, and mean(me) reads it, so an online host carries one vector for it and no rows. Its batch form over a matrix runs the same recursion from the first level, and the two agree exactly over the same rows.

Mathematical definition

\[\begin{align} \boldsymbol{p}_{t} &= \boldsymbol{1}\,,\quad \boldsymbol{p}_{t-k} = \boldsymbol{p}_{t-k+1} \oslash (\boldsymbol{1} + \boldsymbol{r}_{t-k+1})\,,\quad k = 1, \ldots, w - 1\,,\\ \hat{\boldsymbol{\mu}} &= \mathrm{stat}(\boldsymbol{p}_{t-w+1}, \ldots, \boldsymbol{p}_{t}) \oslash \boldsymbol{p}_{t} - \boldsymbol{1}\,. \end{align}\]

Where:

  • $\boldsymbol{p}_t$: Price level vector of period $t$, one entry per asset. Here the levels are reconstructed from the returns, with the last level at one.
  • $\boldsymbol{r}_{t}$: Returns vector of period $t$, one entry per asset.
  • $w$: Price-level window, the number of price levels a statistic reads, the current one included.
  • $\mathrm{stat}$: The statistic of alg, a vector over the assets.
  • $\hat{\boldsymbol{\mu}}$: The expected return, one entry per asset.
  • $\hat{\boldsymbol{x}}_{t+1}$: Price Relative Forecast for period $t + 1$, made after the row of period $t$.

The expected return is the Price Relative Forecast minus one, $\hat{\boldsymbol{\mu}} = \hat{\boldsymbol{x}}_{t+1} - \boldsymbol{1}$.

Fields

  • alg: The price-level statistic the forecast reads.
  • cache: Optional partial-fit state. It is nothing until partial_fit! writes one, and the estimator's read-out verb reads it when the caller gives no data matrix. Each propagation channel does one thing with it: factory carries it unchanged, because a factory call resolves configuration rather than the sample; port_opt_view slices it to the selected assets by index copy, so the viewed estimator answers over those assets alone; and obs_weights_view drops it, because no slice of a state exists on the observation axis. That channel is open only on a type with an observation-weights field, and a type with none falls through to the identity, so its state is carried. A family whose state has no exact asset slice drops it on both axes and names the reason.

Constructors

PriceLevelExpectedReturns(;    alg::AbstractPriceLevelStatistic = MovingAverage(),    cache::Option{<:PriceLevelForecastState} = nothing) -> PriceLevelExpectedReturns

Keywords correspond to the struct's fields.

View parameters

PriceLevelExpectedReturns defines its own port_opt_view method rather than deriving one from field tags.

  • alg passes through unchanged, because a statistic holds no per-asset data.
  • cache recurses through port_opt_view of PriceLevelForecastState, which slices the count, the statistic and the columns of the memory to the selected assets. The number of rows folded stays.

Examples

julia> PriceLevelExpectedReturns()PriceLevelExpectedReturns  alg ┼ MovingAverage      │   window ┴ Int64: 5

Related

source
PortfolioOptimisers.PriceLevelForecastState — Type
struct PriceLevelForecastState{__T_n, __T_nu, __T_stat, __T_hist} <: AbstractPartialFitState

The Partial Fit State of a PriceLevelExpectedReturns whose statistic folds.

It holds the number of rows folded, the number of levels each asset has folded since its last reset, the statistic in units of the last level, and the memory of a statistic that carries one. The state is not merged, because its recursion depends on the order of the rows. Two states folded on different rows have no common continuation.

Fields

  • n: Number of observations folded into the state.
  • nu: The number of levels each asset has folded since its last reset, assets × 1. An asset at zero carries the cold seed, and the read-out answers NaN for it.
  • stat: The folded statistic divided by the current level, assets × 1: the Price Relative Forecast.
  • hist: The last memory_rows relatives folded, rows × assets, the newest last, or nothing for a statistic that carries no memory.

Related

source
PortfolioOptimisers.rows_needed — Method
rows_needed(me::PriceLevelExpectedReturns)
rows_needed(me::AbstractExpectedReturnsEstimator)
rows_needed(me::WindowedExpectedReturns)
rows_needed(alg::AbstractPriceLevelStatistic)

Returns the number of return rows an expected-returns estimator reads at a step of the online portfolio selection family, 1 for one that folds, or nothing when it reads every row folded so far.

A windowed price-level statistic over window levels reads window - 1 returns. A folding statistic reads the current row alone, because its state is one vector. The head gives it that row as it stands, with its gaps and its active mask, as a one-row carrier, and not the finite price relative of the step. An estimator for which supports_partial_fit returns true has an exact fold of its own, and it reads the current row on the same terms. A WindowedExpectedReturns reads its window.

Every other expected-returns estimator returns nothing, which is unbounded. The head keeps every row for it, and it refits on the whole prefix at every step, at a cost of O(tN) a step after t rows. ForecastReversion forwards to its fields, and the head takes the maximum over its rule tree.

Related

source
PortfolioOptimisers.window_rows — Function
window_rows(alg::AbstractPriceLevelStatistic)
window_rows(alg::ExponentialMovingAverage)
window_rows(alg::LaggedPrice)
window_rows(alg::ReweightedPriceRelative)
window_rows(alg::GaussianWeightedDoubleEstimate)
window_rows(test::AbstractTrendTest)
window_rows(alg::TrendSwitch)
window_rows(alg::CompositeTrend)
window_rows(alg::KernelTrendPattern)

Returns the number of return rows the batch form of a statistic reads, or nothing for one that reads every row it gets.

A statistic over window levels reads window - 1 rows, which is the default, and so does a trend test. The lagged price reads lag rows. A folding statistic reads every row, because its batch form is the recursion from the first level. The Gaussian double estimate reads the number of levels in its Gaussian window. A trend switch reads the largest number of its test and its branches, and a composite trend reads window - 1 rows more than its widest trend. Either of them reads every row when one of its members does.

Related

source
PortfolioOptimisers.price_level_statistic — Function
price_level_statistic(alg::MovingAverage, P::AbstractMatrix)
price_level_statistic(alg::ExponentialMovingAverage, P::AbstractMatrix)
price_level_statistic(alg::SpatialMedian, P::AbstractMatrix)
price_level_statistic(alg::WindowPeak, P::AbstractMatrix)
price_level_statistic(alg::LaggedPrice, P::AbstractMatrix)
price_level_statistic(alg::ReweightedPriceRelative, P::AbstractMatrix)
price_level_statistic(alg::TruncatedExponentialMovingAverage, P::AbstractMatrix)
price_level_statistic(alg::GaussianWeightedDoubleEstimate, P::AbstractMatrix)
price_level_statistic(alg::TrendSwitch, P::AbstractMatrix)
price_level_statistic(alg::CompositeTrend, P::AbstractMatrix)
price_level_statistic(alg::KernelTrendPattern, P::AbstractMatrix)

Returns the statistic of a window of price levels, one value per asset.

Each composite statistic states its own statistic in its # Mathematical definition, and its method returns that statistic times the last level. A trend switch and a composite trend read each member over the member's own window with member_statistic. The kernel trend pattern runs its recursion through fold_levels.

Every statistic is homogeneous of degree one when all the levels are scaled by one number. The moving average is the column mean of the levels. The exponential moving average runs its recursion over every row from the first. The spatial median is the point that minimises the sum of Euclidean distances to the rows, which spatial_median finds. The window peak is the column maximum. The lagged price is the row lag above the last, or the first row when the window truncates. The reweighted relative runs its recursion over the relatives of successive rows through fold_levels, and multiplies the result by the last level.

Arguments

  • alg: The statistic.
  • P: The levels, levels × assets, the last row the current level.

Returns

  • stat::Vector: The statistic per asset.

Related

source
PortfolioOptimisers.fold_statistic — Function
fold_statistic(alg::ExponentialMovingAverage, stat, x::AbstractVector)
fold_statistic(alg::ReweightedPriceRelative, stat, x::AbstractVector)
fold_statistic(alg::KernelTrendPattern, stat, hist::AbstractMatrix, x::AbstractVector)

Runs one row of the recursion of a folding statistic, in units of the current level.

The four-argument form is the recursion of a statistic with a memory. It gets the last memory_rows relatives with the row appended, and the fold and the batch choose the arity by the memory. KernelTrendPattern states its own recursion.

Mathematical definition

\[\begin{align} \hat{\boldsymbol{x}}' &= \alpha \boldsymbol{1} + (1 - \alpha)\, \hat{\boldsymbol{x}} \oslash \boldsymbol{x}\,,\quad \hat{\boldsymbol{x}}_{\mathrm{cold}} = \boldsymbol{1}\,,\\ \boldsymbol{\gamma} &= \theta \boldsymbol{x} \oslash (\theta \boldsymbol{x} + \hat{\boldsymbol{\varphi}})\,,\quad \hat{\boldsymbol{\varphi}}' = \boldsymbol{\gamma} + (\boldsymbol{1} - \boldsymbol{\gamma}) \odot \hat{\boldsymbol{\varphi}} \oslash \boldsymbol{x}\,,\quad \hat{\boldsymbol{\varphi}}_{\mathrm{cold}} = \boldsymbol{x}\,. \end{align}\]

Where:

  • $\hat{\boldsymbol{x}}$, $\hat{\boldsymbol{x}}'$: The forecast of ExponentialMovingAverage before and after the row.
  • $\hat{\boldsymbol{\varphi}}$, $\hat{\boldsymbol{\varphi}}'$: The forecast of ReweightedPriceRelative before and after the row.
  • $\boldsymbol{x}$: The price relative of the row.
  • $\alpha$: Smoothing weight on the current level, in $(0, 1]$. A smaller weight gives a longer memory. It is the weight of the exponential moving average.
  • $\theta$, $\boldsymbol{\gamma}$: The reweighting strength and the per-asset weight of the reweighted relative.
  • $\hat{\boldsymbol{x}}_{\mathrm{cold}}$, $\hat{\boldsymbol{\varphi}}_{\mathrm{cold}}$: The carried value before the first row, which cold_statistic returns.

The reweighted relative after the first row is one in every asset, because $\hat{\boldsymbol{\varphi}}_{\mathrm{cold}} = \boldsymbol{x}$ gives $\hat{\boldsymbol{\varphi}}' = \boldsymbol{1}$.

Arguments

  • alg: The statistic.
  • stat: The carried statistic, or nothing before the first row.
  • hist: The carried memory with the row appended, or nothing.
  • x: The price relative of the row, 1 .+ r.

Returns

  • stat'::Vector: The statistic after the row, a new vector.

Related

source
PortfolioOptimisers.cold_statistic — Function
cold_statistic(alg::AbstractPriceLevelStatistic, x::AbstractVector)
cold_statistic(alg::ReweightedPriceRelative, x::AbstractVector)
cold_statistic(alg::KernelTrendPattern, x::AbstractVector)

Returns the carried statistic of a folding statistic before its first row, in the units that its recursion reads.

It is the value for which fold_statistic returns the seed that the statistic states. The exponential moving average seeds its forecast at one and reads the carried vector as it stands, so its cold seed is one in every asset. The reweighted relative seeds at the row's own relative. The kernel trend pattern divides the carried prediction by that relative to bring it into the units of the current level. So both of them seed at x.

partial_fit! writes this value at an asset that the active mask turns off, so a relisted asset starts the recursion again from its seed.

Arguments

  • alg: The folding statistic.
  • x: The price relative of the row, 1 .+ r.

Returns

  • stat::Vector: The cold seed per asset, a new vector.

Related

source
Statistics.mean — Method
Statistics.mean(me::PriceLevelExpectedReturns, X::MatNum; dims::Int = 1,
                active_mask::Option{<:AbstractMatrix{<:Bool}} = nothing, kwargs...)

Computes the expected return as the ratio of a price-level statistic to the last price level, minus one.

A windowed statistic reads no active mask. The Asset Panel method reduces it to the Coverage Universe of its window. A folding statistic runs its recursion through partial_fit!, so the batch and the fold are the same code over the same rows, and they agree exactly. The batch reads a gap and a relisting as the fold does. An asset that the mask turns off starts from its seed when it relists, and an asset that has folded no level is NaN.

Algorithm

  1. Orient X and active_mask so that the observations are rows, and check them.
  2. For a folding statistic, fold every row of X into a new estimator with partial_fit! under the mask, from no state, and read mu from its state.
  3. For a windowed statistic, refuse a mask. Take the last window_rows rows of X, or every row when X holds fewer, and reconstruct their levels P with price_levels.
  4. Read the statistic of P with price_level_statistic, and subtract one, which is the last level, giving mu.
  5. Shape mu as dims asks.

Arguments

  • me: The estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • active_mask: The active mask of the Asset Panel, observations × assets, or nothing. A windowed statistic refuses one.
  • kwargs: Additional keyword arguments (ignored).

Validation

  • dims in (1, 2).
  • X holds at least one observation. An IsEmptyError is thrown otherwise.
  • If active_mask is not nothing, size(X) == size(active_mask). A DimensionMismatch is thrown otherwise.
  • me.alg folds when active_mask is given. An ArgumentError is thrown otherwise.

Returns

  • mu::Matrix{<:Number}: The expected return, shaped as (1, N) if dims == 1 or (N, 1) if dims == 2.

Related

source
Statistics.mean — Method
mean(
    me::PriceLevelExpectedReturns,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    pnl::Union{Nothing, AssetPanel};
    dims,
    kwargs...
) -> Any

Computes the expected return of a price-level statistic on an Asset Panel.

A folding statistic reads the active mask, so this method replaces the reduce-and-expand root of the verb for it, and passes the mask of the panel. The result then covers the whole universe, not only the Coverage Universe. An asset that lists inside the window gets a forecast from the levels it has, and it is NaN only while it has folded no level. A windowed statistic takes the root. The method fits it on the Coverage Universe of the window and fills NaN outside it. A composite statistic is windowed even when it holds a folding member. So it is NaN at an asset with a gap anywhere in its window, while the bare folding member admits that asset again one row after the gap.

Algorithm

  1. For a windowed statistic, reduce X to the Coverage Universe of the panel, compute the expected return there, and expand it with NaN to the whole universe.
  2. For a folding statistic, read the active mask of the panel with panel_moment_masks, and orient it as X is oriented.
  3. Compute the expected return of X under that mask.

Arguments

  • me: The estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • pnl: Optional AssetPanel, whose active mask the Coverage Universe of the fit is derived from. nothing makes the rule finiteness alone.
  • dims: Dimension along which to perform the computation.
  • kwargs: Additional keyword arguments (ignored).

Returns

  • mu::Matrix{<:Number}: The expected return, shaped as (1, N) if dims == 1 or (N, 1) if dims == 2.

Related

source
Statistics.mean — Method
Statistics.mean(me::PriceLevelExpectedReturns; kwargs...)

Reads the expected return from the state that a folding statistic carries, as the folded statistic minus one.

An asset that has folded no level since its last reset carries its cold seed, not a forecast. So the read-out is NaN for it, as ExpWeightedExpectedReturns is below its min_obs. One folded level is enough for a forecast, so a NaN marks an asset that the mask turned off, or one that has no return yet.

Validation

  • me.cache is not nothing. An ArgumentError is thrown otherwise.

Returns

  • mu::Matrix{<:Number}: The expected return, shaped (1, N).

Related

source
PortfolioOptimisers.partial_fit! — Method
partial_fit!(
    est::Union{AbstractEstimator, CovarianceEstimator},
    X::Union{AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}};
    dims,
    active_mask,
    estimation_mask
) -> Any

Folds observations into the sample buffer an estimator carries.

This is the buffering arm of partial_fit!. Each estimator that carries a SampleBufferState reaches this method. A family that folds exactly has methods of its own, and each of them narrows the cache type parameter of its estimator to the state of that fold. So a buffer never reaches them, and it reaches this method. The type of the state selects the route, and no method refuses the step.

The method covers both arms of the interface. The families that refuse the step also declare one method over both arms, and two narrower methods here would be ambiguous against each of them. So the body selects the arm by the type of X, and the compiler resolves that branch at each call site.

A buffer carries the per-observation masks beside the observations. So a CoveragePolicy mask goes through the wrapper as it goes through the accumulator of an estimator, and the read-out gives it back to the batch verb. A wrapped estimator folded under a policy gives the answer of a batch fit over the same window under the same policy.

Algorithm

  1. Read the buffer out of the cache field with assert_sample_buffer, which refuses an estimator that was never wrapped in Online.
  2. Fold a matrix and its masks through the block arm of partial_fit!, and a vector and its masks through the single-observation arm.
  3. Rebind est.cache with Accessors.@reset, and return the estimator.

Arguments

  • est: Estimator whose buffer is folded forward.
  • X: Observations to fold. A matrix holds one observation per row when dims == 1, and one per column when dims == 2. A vector is a single observation across the assets, and dims is ignored.
  • dims: Dimension along which to perform the computation.
  • active_mask: The active mask of the block, of the shape of X, or of one entry per asset when X is one observation, or nothing.
  • estimation_mask: The estimation mask, on the same terms as active_mask.

Validation

  • est carries a SampleBufferState. An ArgumentError is thrown otherwise.
  • The masks, when they are not nothing, have the shape of X. A DimensionMismatch is thrown otherwise.
  • A buffer that holds observations receives the masks that it records. An ArgumentError is thrown otherwise.
  • dims in (1, 2).

Returns

  • est: The estimator, with its cache field rebound to the buffer after the last observation.

Related

source
partial_fit!(me::PriceLevelExpectedReturns, x::VecNum;
             active_mask::Option{<:AbstractVector{<:Bool}} = nothing, kwargs...)
partial_fit!(me::PriceLevelExpectedReturns, X::MatNum; dims::Int = 1,
             active_mask::Option{<:AbstractMatrix{<:Bool}} = nothing, kwargs...)

Folds one row of returns, or a block of rows in order, into the state that a folding statistic carries.

The first row seeds the state, and the fold resets an asset that the active mask turns off. An asset is valid at a row when its return is finite and the mask admits it, which is the condition that the exponentially weighted family reads. With no mask the finite assets are the active ones. So a gap is a delisting and not a holiday, because nothing else states which it is. The block method folds its rows one at a time through the row method.

Algorithm

  1. Refuse a statistic that does not fold, and a mask without one entry per asset. Read the valid assets and the active ones.
  2. Turn the row into the price relative 1 .+ x, with one wherever the asset is not valid. The level did not move, which is how a holiday inside a listing reads.
  3. Append the relative to the memory of a statistic that carries one, through push_memory. Set the memory of every inactive asset to one, so its levels carry nothing from before the delisting.
  4. Write cold_statistic at every inactive asset, and at every asset that has folded no level, so a relisted asset starts the recursion from its seed.
  5. Fold the active assets alone with fold_active.
  6. Add one to the count of every valid asset, zero the count of every inactive one, and add one to the number of rows folded.

Arguments

  • me: The estimator.
  • x: One row of returns, one entry per asset.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • active_mask: The active mask of the Asset Panel at this row, or over the block, or nothing.
  • kwargs: Additional keyword arguments (ignored).

Validation

  • me.alg folds. An ArgumentError is thrown otherwise, through assert_folding_statistic.
  • If active_mask is not nothing, it holds one entry per asset. A DimensionMismatch is thrown otherwise.
  • dims in (1, 2).

Returns

  • me::PriceLevelExpectedReturns: The estimator carrying the state after the last row.

Related

source

References

[48]
B. Li and S. C. Hoi. On-line portfolio selection with moving average reversion. In: Proceedings of the 29th International Conference on Machine Learning (2012).
[49]
B. Li, S. C. Hoi, D. Sahoo and Z.-Y. Liu. Moving average reversion strategy for on-line portfolio selection. Artificial Intelligence 222, 104–123 (2015).
[50]
D. Huang, J. Zhou, B. Li, S. C. Hoi and S. Zhou. Robust median reversion strategy for online portfolio selection. IEEE Transactions on Knowledge and Data Engineering 28, 2480–2493 (2016).
[51]
Y. Vardi and C.-H. Zhang. The multivariate L1-median and associated data depth. Proceedings of the National Academy of Sciences 97, 1423–1426 (2000).
[52]
Z.-R. Lai, D.-Q. Dai, C.-X. Ren and K.-K. Huang. A peak price tracking-based learning system for portfolio selection. IEEE Transactions on Neural Networks and Learning Systems 29, 2823–2832 (2018).
[53]
Z.-R. Lai, P.-Y. Yang, L. Fang and X. Wu. Short-term sparse portfolio optimization based on alternating direction method of multipliers. Journal of Machine Learning Research 19, 1–28 (2018).
[54]
B. Li, J. Luo and H. Xu. A portfolio selection strategy based on the peak price involving randomness. IEEE Access 11, 52066–52074 (2023).
[55]
B. Li, J. Wang, D. Huang and S. C. Hoi. Transaction cost optimization for online portfolio selection. Quantitative Finance 18, 1411–1424 (2018).
[56]
Z.-R. Lai, P.-Y. Yang, L. Fang and X. Wu. Reweighted price relative tracking system for automatic portfolio optimization. IEEE Transactions on Systems, Man, and Cybernetics: Systems 50, 4349–4361 (2020).