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 <: AbstractExpectedReturnsAlgorithmAbstract 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 levelsP,levels × assets, whose last row is the current level. A folding statistic runs its recursion over every row ofPfrom the first.window_rows(alg::AbstractPriceLevelStatistic) -> Union{Nothing, Integer}: The number of return rows the batch form reads to reconstruct its levels. It iswindow - 1for a statistic overwindowlevels, andnothingfor one that reads every row it gets. The default readsalg.window - 1.folds(alg::AbstractPriceLevelStatistic) -> Bool:truewhen the statistic is an exact recursion over price relatives, whichfold_statisticcarries. The default isfalse.fold_statistic(alg::AbstractPriceLevelStatistic, stat, x::AbstractVector) -> AbstractVector: The recursion of a folding statistic. It takes the carried statisticstatin relative terms,nothingbefore the first row, and the price relativexof 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 is0, for a recursion that reads the carried vector alone. A statistic with a memory implements the four-argumentfold_statistic(alg, stat, hist, x)instead. Therehistholds the lastmemory_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, ornothing.hist: The carried memory of relatives,rows × assets, the last row beingx.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
PortfolioOptimisers.MovingAverage — Type
struct MovingAverage{T1<:Integer} <: AbstractPriceLevelStatisticAverages 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 - 1returns reconstruct them.
Constructors
MovingAverage(; window::Integer = 5) -> MovingAverageKeywords correspond to the struct's fields.
Validation
window >= 2. ADomainErroris thrown otherwise: one level is the current price, and its ratio to itself forecasts nothing.
Examples
julia> MovingAverage()MovingAverage window ┴ Int64: 5Related
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).
PortfolioOptimisers.ExponentialMovingAverage — Type
struct ExponentialMovingAverage{T1<:Real} <: AbstractPriceLevelStatisticAverages 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) -> ExponentialMovingAverageKeywords correspond to the struct's fields.
Validation
0 < alpha <= 1. ADomainErroris thrown otherwise.
Examples
julia> ExponentialMovingAverage()ExponentialMovingAverage alpha ┴ Float64: 0.5Related
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).
PortfolioOptimisers.SpatialMedian — Type
struct SpatialMedian{T1<:Integer, T2<:Integer, T3<:Real} <: AbstractPriceLevelStatisticTakes 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 - 1returns 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) -> SpatialMedianKeywords correspond to the struct's fields.
Validation
window >= 2. ADomainErroris thrown otherwise.iters >= 1. ADomainErroris thrown otherwise.tol > 0. ADomainErroris thrown otherwise.
Examples
julia> SpatialMedian()SpatialMedian window ┼ Int64: 5 iters ┼ Int64: 100 tol ┴ Float64: 1.0e-8Related
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).
PortfolioOptimisers.WindowPeak — Type
struct WindowPeak{T1<:Integer} <: AbstractPriceLevelStatisticTakes 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 - 1returns reconstruct them.
Constructors
WindowPeak(; window::Integer = 5) -> WindowPeakKeywords correspond to the struct's fields.
Validation
window >= 2. ADomainErroris thrown otherwise.
Examples
julia> WindowPeak()WindowPeak window ┴ Int64: 5Related
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).
PortfolioOptimisers.LaggedPrice — Type
struct LaggedPrice{T1<:Integer} <: AbstractPriceLevelStatisticTakes 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,0for the current level.
Constructors
LaggedPrice(; lag::Integer = 1) -> LaggedPriceKeywords correspond to the struct's fields.
Validation
lag >= 0. ADomainErroris thrown otherwise.
Examples
julia> LaggedPrice()LaggedPrice lag ┴ Int64: 1Related
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.
PortfolioOptimisers.ReweightedPriceRelative — Type
struct ReweightedPriceRelative{T1<:Real} <: AbstractPriceLevelStatisticAverages 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 strengththeta > 0on the current price relative.
Constructors
ReweightedPriceRelative(; theta::Real = 0.8) -> ReweightedPriceRelativeKeywords correspond to the struct's fields.
Validation
theta > 0. ADomainErroris thrown otherwise.
Examples
julia> ReweightedPriceRelative()ReweightedPriceRelative theta ┴ Float64: 0.8Related
AbstractPriceLevelStatisticPriceLevelExpectedReturnsExponentialMovingAverageReweightedPriceRelativeTrackingfold_statistic
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).
PortfolioOptimisers.PriceLevelExpectedReturns — Type
struct PriceLevelExpectedReturns{T1<:AbstractPriceLevelStatistic, T2<:(Union{Nothing, var"#s1800"} where var"#s1800"<:PriceLevelForecastState)} <: AbstractExpectedReturnsEstimatorForecasts 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 isnothinguntilpartial_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:factorycarries it unchanged, because a factory call resolves configuration rather than the sample;port_opt_viewslices it to the selected assets by index copy, so the viewed estimator answers over those assets alone; andobs_weights_viewdrops 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) -> PriceLevelExpectedReturnsKeywords correspond to the struct's fields.
View parameters
PriceLevelExpectedReturns defines its own port_opt_view method rather than deriving one from field tags.
algpasses through unchanged, because a statistic holds no per-asset data.cacherecurses throughport_opt_viewofPriceLevelForecastState, 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: 5Related
PortfolioOptimisers.PriceLevelForecastState — Type
struct PriceLevelForecastState{__T_n, __T_nu, __T_stat, __T_hist} <: AbstractPartialFitStateThe 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 answersNaNfor it.
stat: The folded statistic divided by the current level,assets × 1: the Price Relative Forecast.
hist: The lastmemory_rowsrelatives folded,rows × assets, the newest last, ornothingfor a statistic that carries no memory.
Related
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
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
PortfolioOptimisers.folds — Function
folds(alg::AbstractPriceLevelStatistic)
folds(alg::ExponentialMovingAverage)
folds(alg::ReweightedPriceRelative)
folds(alg::KernelTrendPattern)Returns true when a statistic is an exact recursion over price relatives, and false by default.
A composite statistic does not fold, even when a member does, because it reads each member over a window of levels.
Related
PortfolioOptimisers.memory_rows — Function
memory_rows(alg::AbstractPriceLevelStatistic)
memory_rows(alg::KernelTrendPattern)Returns the number of relatives a folding statistic carries as its memory on the state.
The default is 0, for a statistic whose recursion reads the carried vector alone. The kernel trend pattern carries 2 window relatives.
Related
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
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
ExponentialMovingAveragebefore and after the row. - $\hat{\boldsymbol{\varphi}}$, $\hat{\boldsymbol{\varphi}}'$: The forecast of
ReweightedPriceRelativebefore 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_statisticreturns.
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, ornothingbefore the first row.hist: The carried memory with the row appended, ornothing.x: The price relative of the row,1 .+ r.
Returns
stat'::Vector: The statistic after the row, a new vector.
Related
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
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
- Orient
Xandactive_maskso that the observations are rows, and check them. - For a folding statistic, fold every row of
Xinto a new estimator withpartial_fit!under the mask, from no state, and readmufrom its state. - For a windowed statistic, refuse a mask. Take the last
window_rowsrows ofX, or every row whenXholds fewer, and reconstruct their levelsPwithprice_levels. - Read the statistic of
Pwithprice_level_statistic, and subtract one, which is the last level, givingmu. - Shape
muasdimsasks.
Arguments
me: The estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.dims: Dimension along which to perform the computation.active_mask: The active mask of the Asset Panel,observations × assets, ornothing. A windowed statistic refuses one.kwargs: Additional keyword arguments (ignored).
Validation
dims in (1, 2).Xholds at least one observation. AnIsEmptyErroris thrown otherwise.- If
active_maskis notnothing,size(X) == size(active_mask). ADimensionMismatchis thrown otherwise. me.algfolds whenactive_maskis given. AnArgumentErroris thrown otherwise.
Returns
mu::Matrix{<:Number}: The expected return, shaped as(1, N)ifdims == 1or(N, 1)ifdims == 2.
Related
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
- For a windowed statistic, reduce
Xto the Coverage Universe of the panel, compute the expected return there, and expand it withNaNto the whole universe. - For a folding statistic, read the active mask of the panel with
panel_moment_masks, and orient it asXis oriented. - Compute the expected return of
Xunder that mask.
Arguments
me: The estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.pnl: OptionalAssetPanel, whose active mask the Coverage Universe of the fit is derived from.nothingmakes 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)ifdims == 1or(N, 1)ifdims == 2.
Related
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.cacheis notnothing. AnArgumentErroris thrown otherwise.
Returns
mu::Matrix{<:Number}: The expected return, shaped(1, N).
Related
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
- Read the buffer out of the
cachefield withassert_sample_buffer, which refuses an estimator that was never wrapped inOnline. - Fold a matrix and its masks through the block arm of
partial_fit!, and a vector and its masks through the single-observation arm. - Rebind
est.cachewithAccessors.@reset, and return the estimator.
Arguments
est: Estimator whose buffer is folded forward.X: Observations to fold. A matrix holds one observation per row whendims == 1, and one per column whendims == 2. A vector is a single observation across the assets, anddimsis ignored.dims: Dimension along which to perform the computation.active_mask: The active mask of the block, of the shape ofX, or of one entry per asset whenXis one observation, ornothing.estimation_mask: The estimation mask, on the same terms asactive_mask.
Validation
estcarries aSampleBufferState. AnArgumentErroris thrown otherwise.- The masks, when they are not
nothing, have the shape ofX. ADimensionMismatchis thrown otherwise. - A buffer that holds observations receives the masks that it records. An
ArgumentErroris thrown otherwise. dims in (1, 2).
Returns
est: The estimator, with itscachefield rebound to the buffer after the last observation.
Related
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
- Refuse a statistic that does not fold, and a mask without one entry per asset. Read the valid assets and the active ones.
- 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. - 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. - Write
cold_statisticat every inactive asset, and at every asset that has folded no level, so a relisted asset starts the recursion from its seed. - Fold the active assets alone with
fold_active. - 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 matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 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, ornothing.kwargs: Additional keyword arguments (ignored).
Validation
me.algfolds. AnArgumentErroris thrown otherwise, throughassert_folding_statistic.- If
active_maskis notnothing, it holds one entry per asset. ADimensionMismatchis thrown otherwise. dims in (1, 2).
Returns
me::PriceLevelExpectedReturns: The estimator carrying the state after the last row.
Related
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).