The source files can be found in user_guide/.

Data and priors

The first pipeline stage turns raw prices into a prior — the expected-returns vector and covariance matrix every optimiser consumes. Two calls cover the common path: prices_to_returns and prior. This page is the quick tour; for the full menu of moment estimators and view-based priors, see the moments & priors examples.

using PortfolioOptimisers, CSV, TimeSeries, DataFrames, PrettyTables, LinearAlgebra,      StatsPlots, GraphRecipesresfmt = (v, i, j) -> begin    return if j == 1        v    else        isa(v, AbstractFloat) ? "$(round(v*100, digits=3)) %" : v    endend;

1. Prices to returns

Price data usually arrives from an API and must be converted to returns. prices_to_returns handles asset, factor, and benchmark prices (plus implied volatilities and volatility premiums), validates that the series are consistent, and can collapse to lower frequencies (TimeSeries.jl). Given a single TimeArray of prices it returns a ReturnsResult holding the asset names nx and the return matrix X.

Real price tables are rarely clean — newly listed or delisted names leave leading/trailing gaps, halts and stale quotes leave flat stretches, and exchanges keep different holiday calendars. price_ingestion reads each asset's Listing Span off the panel and the conversion carries every gap into the returns, handing back an AssetPanel that says which assets are estimable when; filling a gap is PriceGapFill's and deleting one is MissingDataFilter's. The Data preprocessing and the ingestion layer example is the deep dive.

X = TimeArray(CSV.File(joinpath(@__DIR__, "../examples/SP500.csv.gz")); timestamp = :Date)[(end - 252):end]rd = prices_to_returns(X)
ReturnsResult
    nx ┼ 20-element Vector{String}
     X ┼ 252×20 Matrix{Float64}
    nf ┼ nothing
     F ┼ nothing
    nb ┼ nothing
     B ┼ nothing
    ts ┼ 252-element Vector{Date}
    iv ┼ nothing
  ivpa ┼ nothing
   pnl ┼ AssetPanel
       │     pf ┼ Vector{AbstractPanelField}: AbstractPanelField[]
       │   amsk ┼ 252×20 AllTrueMask
       │   emsk ┴ 252×20 AllTrueMask

That one call is the layer's own path: prices_to_returns(X) on a bare price table is prices_to_returns(price_ingestion(PriceIngestion(), X)). Write the two steps when you hold more than one table — price_ingestion takes factor and benchmark price series as F and B, aligns them onto the asset clock, and the conversion carries the factor returns F and the benchmark returns B through on the same ReturnsResult, so everything downstream has the data it needs. The point-in-time universe takes a gapped table through that path to a walk-forward.

2. Returns to a prior

prior applies a prior estimator to a ReturnsResult and returns the moments. The blessed default is EmpiricalPrior — the sample mean and covariance.

pr = prior(EmpiricalPrior(), rd)pretty_table(DataFrame("Asset" => rd.nx, "Expected return" => pr.mu,                       "Volatility" => sqrt.(diag(pr.sigma))); formatters = [resfmt],             title = "Empirical prior: per-asset mean and volatility")
Empirical prior: per-asset mean and volatility
┌────────┬─────────────────┬────────────┐
│  Asset  Expected return  Volatility │
│ String          Float64     Float64 │
├────────┼─────────────────┼────────────┤
│   AAPL │        -0.113 % │    2.237 % │
│    AMD │        -0.281 % │     3.84 % │
│    BAC │        -0.093 % │     2.04 % │
│    BBY │        -0.028 % │     2.85 % │
│    CVX │         0.195 % │     2.07 % │
│     GE │        -0.034 % │    2.192 % │
│     HD │        -0.071 % │    1.968 % │
│    JNJ │         0.031 % │    1.096 % │
│    JPM │        -0.042 % │    1.877 % │
│     KO │          0.05 % │    1.237 % │
│    LLY │         0.131 % │    1.714 % │
│    MRK │         0.167 % │    1.254 % │
│   MSFT │        -0.121 % │    2.214 % │
│    PEP │         0.039 % │    1.223 % │
│    PFE │        -0.026 % │    1.698 % │
│     PG │        -0.008 % │    1.382 % │
│    RRC │         0.182 % │    3.957 % │
│    UNH │         0.036 % │    1.533 % │
│    WMT │         0.016 % │    1.681 % │
│    XOM │         0.264 % │    2.207 % │
└────────┴─────────────────┴────────────┘

3. Swapping the prior

EmpiricalPrior is only the starting point. Every prior estimator has the same prior(pe, rd) interface, so swapping one in is a one-line change. The common alternatives:

A cross-sectional fit asks more of the caller than the others. It reads no F; it reads an AssetPanel of per-asset Panel Fields — a market capitalisation, a book equity, an industry label — carried on the returns result as rd.pnl, and the caller names the Descriptors and Exposure Estimators that turn those fields into factors, one Pair per factor. The deep dive is Cross-sectional factor model, end to end, and Cross-sectional factor model through a Pipeline reaches the same weights through a Pipeline.

The covariance estimator inside a prior is itself swappable (shrinkage, denoising, Gerber, …); see Covariance Estimation. Any moment estimator can also be windowed — restricted to a trailing window or recency-weighted — when the recent regime is more informative than the full sample (deep dive: Windowed Estimators; regime-adjusted estimators are a related sibling).

4. A first look at the data

plot_prior summarises a prior in one figure — expected returns, per-asset volatility, and the correlation matrix — a quick sanity check before optimising.

plot_prior(pr, rd)
Example block output

This page was generated using Literate.jl.