Tracking

PortfolioOptimisers.AbstractTrackingAlgorithmType
abstract type AbstractTrackingAlgorithm <: AbstractAlgorithm

Abstract supertype for all tracking algorithm types.

All concrete and/or abstract types representing tracking algorithms (such as weights or returns tracking) should be subtypes of AbstractTrackingAlgorithm.

Interfaces

In order to implement a new tracking algorithm that works seamlessly with the library, subtype AbstractTrackingAlgorithm and implement the following methods:

  • tracking_benchmark(tr::AbstractTrackingAlgorithm, X::MatNum) -> VecNum: Compute benchmark returns from the asset return matrix X.
  • factory(tr::AbstractTrackingAlgorithm, w::VecNum) -> AbstractTrackingAlgorithm: Construct a new instance with updated portfolio weights w.
  • port_opt_view(tr::AbstractTrackingAlgorithm, i) -> AbstractTrackingAlgorithm: Create a view of the tracking algorithm for the subset of assets at indices i.

Arguments

  • tr: The concrete tracking algorithm instance.
  • X: Covariance-like or correlation-like matrix assets × assets.
  • w: New portfolio weights.
  • i: Index or indices for asset subset.

Returns

  • b::VecNum: Benchmark returns (for tracking_benchmark).
  • tr::AbstractTrackingAlgorithm: Updated or viewed tracking algorithm (for factory, port_opt_view).

Examples

julia> struct MyTracking <: PortfolioOptimisers.AbstractTrackingAlgorithm           w::Vector{Float64}       endjulia> function PortfolioOptimisers.tracking_benchmark(tr::MyTracking,                                                       X::PortfolioOptimisers.MatNum)           return X * tr.w       endjulia> PortfolioOptimisers.factory(tr::MyTracking, w) = MyTracking(w)julia> PortfolioOptimisers.port_opt_view(tr::MyTracking, i) = MyTracking(tr.w[i])julia> tr = MyTracking([0.5, 0.5]);julia> X = [0.01 0.02; 0.03 0.04];julia> PortfolioOptimisers.tracking_benchmark(tr, X)2-element Vector{Float64}: 0.015 0.035

Related

source
PortfolioOptimisers.IndependentVariableTrackingType
struct IndependentVariableTracking <: VariableTracking

Applies the risk measure to the difference between the portfolio weights and the benchmark weights.

The weights are the independent variable of a risk measure, so this formulation compares the two portfolios before the measure is evaluated: it reports $R(\boldsymbol{w} - \boldsymbol{w}_{b})$.

Constructors

IndependentVariableTracking() -> IndependentVariableTracking

Examples

julia> IndependentVariableTracking()IndependentVariableTracking()

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.2.
source
PortfolioOptimisers.DependentVariableTrackingType
struct DependentVariableTracking <: VariableTracking

Applies the risk measure to each portfolio, then takes the absolute difference of the two risks.

The risk is the dependent variable of a risk measure, so this formulation compares the two portfolios after the measure is evaluated: it reports $\left\lvert R(\boldsymbol{w}) - R(\boldsymbol{w}_{b}) \right\rvert$.

Constructors

DependentVariableTracking() -> DependentVariableTracking

Examples

julia> DependentVariableTracking()DependentVariableTracking()

Related

source
PortfolioOptimisers.WeightsTrackingType
struct WeightsTracking{__T_fees, __T_w, __T_fixed} <: AbstractTrackingAlgorithm

Builds the benchmark return series by holding a fixed weight vector, net of its own fees.

The benchmark is stated as a portfolio rather than as a return series, so tracking_benchmark computes $\mathbf{X}\boldsymbol{w}_{b}$ on whichever return matrix the caller supplies. Use it when the benchmark is a known allocation. Use ReturnsTracking when only its realised returns are known.

Mathematical definition

\[\begin{align} \boldsymbol{b} &= \mathbf{X}\boldsymbol{w}_{b} - \boldsymbol{F}(\boldsymbol{w}_{b})\,. \end{align}\]

Where:

  • $\boldsymbol{b}$: T × 1 benchmark return vector.
  • $\mathbf{X}$: T × N asset return matrix.
  • $\boldsymbol{w}_{b}$: N × 1 benchmark weight vector, the w field.
  • $\boldsymbol{F}(\boldsymbol{w}_{b})$: Per-period fee charged on the benchmark, from the fees field. It is zero when fees is nothing. See calc_net_returns.

The fees field reads no fold, and it needs none. charge_fees charges this fee over the benchmark series it builds, so an AmortisedFees fa spreads the two fixed charges over that series, and a nothing fa charges them on its first observation. The count is never stored on the fee, so this site needs no fold to settle one.

Fields

  • fees: Fees estimator or result.
  • w: Reference portfolio weight vector. Deviations are measured against it, and it is never the candidate weight vector an optimiser solves for.
  • fixed: Whether the estimator is fixed and does not update with new weights.

Constructors

WeightsTracking(;    fees::Option{<:Fees} = nothing,    w::VecNum,    fixed::Bool = false) -> WeightsTracking

Keywords correspond to the struct's fields.

Validation

View parameters

When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:

Examples

julia> WeightsTracking(; w = [0.5, 0.5])WeightsTracking   fees ┼ nothing      w ┼ Vector{Float64}: [0.5, 0.5]  fixed ┴ Bool: false

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.2, Equations 9.16 and 9.18.
source
PortfolioOptimisers.ReturnsTrackingType
struct ReturnsTracking{__T_w} <: AbstractTrackingAlgorithm

Carries the benchmark return series itself, for a benchmark whose weights are unknown.

tracking_benchmark returns the w field unchanged, so no return matrix is read and no fee is applied. This is the case the book states first: an index whose published series is all the caller has. Use WeightsTracking when the benchmark allocation is known.

The w field holds T returns, one per observation, not N weights. Its length must match the number of rows of the return matrix the model is built on.

Fields

  • w: Benchmark portfolio returns vector. It holds T returns, one per observation, and not N weights, so its length must match the number of rows of the return matrix the model is built on.

Constructors

ReturnsTracking(;    w::VecNum) -> ReturnsTracking

Keywords correspond to the struct's fields.

Validation

Examples

julia> ReturnsTracking(; w = [0.01, 0.02, 0.03])ReturnsTracking  w ┴ Vector{Float64}: [0.01, 0.02, 0.03]

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.2, Equations 9.16 and 9.17.
source
PortfolioOptimisers.TrackingErrorType
struct TrackingError{__T_tr, __T_err, __T_alg} <: AbstractTracking

Bounds how far the portfolio return series may drift from a benchmark return series.

err is an upper bound, not a computed value: set_tracking_error_constraints! writes one cone per alg and holds the scaled deviation below err. tr supplies the benchmark and alg names the norm that measures the deviation.

Mathematical definition

\[\begin{align} \mathrm{TE}(\boldsymbol{w}) &= \lVert \boldsymbol{r}(\boldsymbol{w}) - \boldsymbol{b} \rVert \cdot c^{-1} \leq \mathrm{err}\,. \end{align}\]

Where:

  • $\mathrm{TE}(\boldsymbol{w})$: Tracking error.
  • $\boldsymbol{r}(\boldsymbol{w})$: T × 1 net portfolio return series.
  • $\boldsymbol{b}$: T × 1 benchmark return series, from tracking_benchmark on the tr field.
  • $\lVert \cdot \rVert$, $c$: The norm and the scaling factor that alg names. norm_error computes the pair and norm_factor gives $c$.
  • $\mathrm{err}$: The err field.
Warning

err is stated in the units of alg, and SquaredL2Norm squares. The same number therefore means two different bounds: TrackingError(; alg = SquaredL2Norm(), err = 5e-6) admits an L2Norm error up to sqrt(5e-6), where TrackingError(; alg = L2Norm(), err = 5e-6) admits 5e-6. Convert with the square, not by reusing the tolerance. The model, norm_error and set_risk_constraints! all read err the same way.

The conversion is the square root, it carries no dependence on T, and the two norms then write the same cone bound. tracking_error_soc_factor is where they meet: (SquaredL2Norm(), err^2) and (L2Norm(), err) give one factor, one weight vector, and realised deviations that satisfy the square. The ddof field of alg moves that bound.

The keys the model registers are picked by alg, and each carries the constraint index appended. Every branch registers :t_te_ for the cone variable, :te_ for the deviation expression $\mathbf{X}\boldsymbol{w} - \boldsymbol{b}k$, and :cte_ for the row that holds the cone variable below the scaled tolerance. The cone, its row, and the rows a branch adds beyond those three, are:

algConeCone rowRows the branch adds
L1NormJuMP.MOI.NormOneCone:cte_noc_none
L2Norm, SquaredL2NormJuMP.SecondOrderCone:cte_soc_none
LpNormJuMP.MOI.PowerCone:cte_pnorm_:r_te_, :cste_
LInfNormJuMP.MOI.NormInfinityCone:cte_infnorm_none

:cte_soc_ is therefore the key of the default alg = L2Norm() and of SquaredL2Norm alone. The model registers no :tracking_risk_ and no :sq_tracking_risk_: those two keys belong to TrackingRiskMeasure, which measures a risk difference rather than a return-series deviation.

Fields

  • tr: Tracking error constraint estimator.
  • err: Tracking error tolerance.
  • alg: Tracking formulation algorithm.

Constructors

TrackingError(;    tr::AbstractTrackingAlgorithm,    err::Number = 0.0,    alg::NormError = L2Norm()) -> TrackingError

Keywords correspond to the struct's fields.

Validation

  • err, through assert_nonempty_nonneg_finite_val: isfinite(err) and err >= 0, each raising a DomainError naming err. err = 0.0 is admitted, and it pins the portfolio to the benchmark.

Propagated parameters

When factory is called on this type, the following @fprop-tagged fields are automatically propagated:

  • tr: Recursively updated via factory.

View parameters

When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:

Examples

julia> tr = WeightsTracking(; w = [0.5, 0.5]);julia> TrackingError(; tr = tr, err = 0.01)TrackingError   tr ┼ WeightsTracking      │    fees ┼ nothing      │       w ┼ Vector{Float64}: [0.5, 0.5]      │   fixed ┴ Bool: false  err ┼ Float64: 0.01  alg ┼ L2Norm      │   ddof ┴ Int64: 1

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.2, Equations 9.19 to 9.21.
source
PortfolioOptimisers.factoryMethod
factory(tr::WeightsTracking, w::VecNum)

Construct a new WeightsTracking object with updated portfolio weights.

The fixed flag decides whether the benchmark moves, and it obeys it the way factory(tn::Turnover, w::VecNum) obeys its own. A fixed benchmark is a fixed allocation, so the object is returned unchanged and === holds. A benchmark that is not fixed takes w as its new reference, and its fees advance one step: the nested Turnover takes the old tr.w as its reference, because that is the allocation the portfolio is turning over from.

Algorithm

  1. Read tr.fixed.
  2. When tr.fixed is true, return tr itself. The argument w is not read.
  3. When tr.fixed is false, advance the fees with factory(tr.fees, tr.w), which hands the old weights to the nested turnover as its reference.
  4. Build a new WeightsTracking from the advanced fees, the new w, and the unchanged tr.fixed.

Arguments

Returns

  • tr::WeightsTracking: New tracking algorithm object with updated weights, or tr itself when tr.fixed is true.

Examples

julia> tr = WeightsTracking(; fees = Fees(; l = 0.002), w = [0.5, 0.5])WeightsTracking   fees ┼ Fees        │       tn ┼ nothing        │        l ┼ Float64: 0.002        │        s ┼ nothing        │       fl ┼ nothing        │       fs ┼ nothing        │       lq ┼ nothing        │      flq ┼ nothing        │       fa ┼ nothing        │   kwargs ┴ @NamedTuple{atol::Float64}: (atol = 1.0e-8,)      w ┼ Vector{Float64}: [0.5, 0.5]  fixed ┴ Bool: falsejulia> PortfolioOptimisers.factory(tr, [0.6, 0.4])WeightsTracking   fees ┼ Fees        │       tn ┼ nothing        │        l ┼ Float64: 0.002        │        s ┼ nothing        │       fl ┼ nothing        │       fs ┼ nothing        │       lq ┼ nothing        │      flq ┼ nothing        │       fa ┼ nothing        │   kwargs ┴ @NamedTuple{atol::Float64}: (atol = 1.0e-8,)      w ┼ Vector{Float64}: [0.6, 0.4]  fixed ┴ Bool: falsejulia> tr = WeightsTracking(; fees = Fees(; l = 0.002), w = [0.5, 0.5], fixed = true)WeightsTracking   fees ┼ Fees        │       tn ┼ nothing        │        l ┼ Float64: 0.002        │        s ┼ nothing        │       fl ┼ nothing        │       fs ┼ nothing        │       lq ┼ nothing        │      flq ┼ nothing        │       fa ┼ nothing        │   kwargs ┴ @NamedTuple{atol::Float64}: (atol = 1.0e-8,)      w ┼ Vector{Float64}: [0.5, 0.5]  fixed ┴ Bool: truejulia> PortfolioOptimisers.factory(tr, [0.1, 0.1])WeightsTracking   fees ┼ Fees        │       tn ┼ nothing        │        l ┼ Float64: 0.002        │        s ┼ nothing        │       fl ┼ nothing        │       fs ┼ nothing        │       lq ┼ nothing        │      flq ┼ nothing        │       fa ┼ nothing        │   kwargs ┴ @NamedTuple{atol::Float64}: (atol = 1.0e-8,)      w ┼ Vector{Float64}: [0.5, 0.5]  fixed ┴ Bool: true

Related

source
PortfolioOptimisers.tracking_benchmarkFunction
tracking_benchmark(tr::WeightsTracking, X::MatNum)

Compute the benchmark portfolio returns for a weights-based tracking algorithm.

tracking_benchmark computes the net portfolio returns for the benchmark weights stored in a WeightsTracking object, optionally adjusting for transaction fees if specified. The asset return matrix X is multiplied by the benchmark weights, and fees are deducted if present.

This method restates no definition of its own. It is calc_net_returns(tr.w, X, tr.fees), so the fee falls on the clock that function reads from tr.fees.fa. On the three-period matrix [0.01 0.02 -0.01 0.03; 0.03 0.04 0.02 -0.02; -0.01 0.005 0.01 0.04] and $\boldsymbol{w}_{b} = [0.3,\, 0.2,\, 0.4,\, 0.1]$, the benchmark measured [0.006, 0.023, 0.006] with no fee, and [0.005, 0.022, 0.005] under Fees(; l = 0.001), whose fee is 0.001.

Algorithm

  1. Forward tr.w, X and tr.fees to calc_net_returns.
  2. A nothing tr.fees reaches the args... method of calc_net_returns, which returns X * tr.w. It does not reach the Fees method and charge a zero fee.
  3. A Fees tr.fees reaches the Fees method, which hands X * tr.w to charge_fees.

Arguments

  • tr: WeightsTracking tracking algorithm containing benchmark weights and optional fees.
  • X: Asset return matrix (observations × assets).

Returns

  • b::VecNum: Net benchmark portfolio returns, one entry per row of X.

Examples

julia> tr = WeightsTracking(; w = [0.5, 0.5]);julia> X = [0.01 0.02; 0.03 0.04];julia> PortfolioOptimisers.tracking_benchmark(tr, X)2-element Vector{Float64}: 0.015 0.035

Related

source
tracking_benchmark(tr::ReturnsTracking, args...)

Return the benchmark portfolio returns for a returns-based tracking algorithm.

tracking_benchmark extracts the benchmark portfolio returns stored in a ReturnsTracking object. This is used for tracking error measurement and constraint generation where the comparison is made directly between portfolio returns and benchmark returns.

No return matrix is read, and no fee is applied. Every trailing argument is ignored, so a matrix whose number of rows does not match length(tr.w) still returns tr.w unchanged: a three-entry w against a seven-row matrix succeeds here. That is deliberate. A length mismatch is a property of the model the series is put into, so it is raised there rather than by this function.

Algorithm

  1. Return tr.w. No argument after tr is read.

Arguments

  • tr: ReturnsTracking tracking algorithm containing benchmark returns.
  • args...: For interface compatibility (ignored).

Returns

  • b::VecNum: Benchmark portfolio returns. It is tr.w itself, not a copy, so tracking_benchmark(tr) === tr.w.

Examples

julia> tr = ReturnsTracking(; w = [0.01, 0.02, 0.03]);julia> PortfolioOptimisers.tracking_benchmark(tr)3-element Vector{Float64}: 0.01 0.02 0.03

Related

source
PortfolioOptimisers.needs_previous_weightsMethod
needs_previous_weights(tr::AbstractTrackingAlgorithm) -> Bool
needs_previous_weights(tr::WeightsTracking) -> Bool
needs_previous_weights(tr::TrackingError) -> Bool
needs_previous_weights(tr::VecTr) -> Bool

Check whether a tracking algorithm or tracking result needs the previous portfolio weights.

Only a WeightsTracking that is not fixed needs them, because only it moves its reference allocation when factory runs. A ReturnsTracking carries a return series and reaches the fallback, which answers false. The vector method answers any and not all, so one entry that needs the previous weights makes the whole vector need them: a vector holding one fixed and one free WeightsTracking answers true where all would answer false.

Algorithm

  1. On any AbstractTrackingAlgorithm that no method below claims, return false.
  2. On a WeightsTracking, return !tr.fixed.
  3. On a TrackingError, forward to the tr.tr field and return its answer.
  4. On a VecTr, apply steps 1 to 3 to every entry and reduce with any.

Arguments

  • tr: One tracking algorithm, one tracking result, or a vector of tracking results.

Returns

  • Bool: true if previous weights are needed, false otherwise.

Related

source

References

[5]
D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).