Skip to content
18

The source files can be found in examples/.

Plotting and reporting

A portfolio is only as useful as your ability to explain it. PortfolioOptimisers.jl ships a plotting layer (loaded by bringing in StatsPlots and GraphRecipes alongside the package) that covers the whole pipeline: the inputs, the allocation, where the risk sits, realised performance, and the risk/return geometry — plus one-call dashboards that assemble several views at once. This page is a guided tour of the reporting toolkit on a single worked portfolio.

When to reach for this

Reach for these at the end of every analysis — to sanity-check inputs before optimising, to communicate an allocation, and to compare candidate strategies on the same axes. The individual plots are building blocks; the dashboards (plot_portfolio_dashboard, plot_performance_summary) are the fastest way to a complete picture.

julia
using PortfolioOptimisers, CSV, TimeSeries, Clarabel, StatsPlots, GraphRecipes

1. A worked portfolio

We build an empirical prior, a minimum-risk and a maximum-ratio book to compare, and an efficient frontier for the geometry plots.

julia
X = TimeArray(CSV.File(joinpath(@__DIR__, "..", "SP500.csv.gz")); timestamp = :Date)[(end - 252):end]
rd = prices_to_returns(X)
pr = prior(EmpiricalPrior(), rd)

slv = Solver(; name = :clarabel, solver = Clarabel.Optimizer,
             settings = Dict("verbose" => false),
             check_sol = (; allow_local = true, allow_almost = true))
rf = 4.2 / 100 / 252

res_min = optimise(MeanRisk(; obj = MinimumRisk(),
                            opt = JuMPOptimiser(; pe = pr, slv = slv)))
res_ratio = optimise(MeanRisk(; obj = MaximumRatio(; rf = rf),
                              opt = JuMPOptimiser(; pe = pr, slv = slv)))
frontier = optimise(MeanRisk(; obj = MinimumRisk(),
                             opt = JuMPOptimiser(; pe = pr, slv = slv,
                                                 ret = ArithmeticReturn(;
                                                                        lb = Frontier(;
                                                                                      N = 15)))))
MeanRiskResult
  jr ┼ JuMPOptimisationResult
     │        pa ┼ ProcessedJuMPOptimiserAttributes
     │           │        pr ┼ LowOrderPrior
     │           │           │         X ┼ 252×20 Matrix{Float64}
     │           │           │        mu ┼ 20-element Vector{Float64}
     │           │           │     sigma ┼ 20×20 Matrix{Float64}
     │           │           │      chol ┼ nothing
     │           │           │         w ┼ nothing
     │           │           │       ens ┼ nothing
     │           │           │       kld ┼ nothing
     │           │           │        ow ┼ nothing
     │           │           │        rr ┼ nothing
     │           │           │      f_mu ┼ nothing
     │           │           │   f_sigma ┼ nothing
     │           │           │       f_w ┴ nothing
     │           │        wb ┼ WeightBounds
     │           │           │   lb ┼ 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}
     │           │           │   ub ┴ 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}
     │           │        lt ┼ nothing
     │           │        st ┼ nothing
     │           │      lcsr ┼ nothing
     │           │       ctr ┼ nothing
     │           │    gcardr ┼ nothing
     │           │   sgcardr ┼ nothing
     │           │      smtx ┼ nothing
     │           │     sgmtx ┼ nothing
     │           │       slt ┼ nothing
     │           │       sst ┼ nothing
     │           │      sglt ┼ nothing
     │           │      sgst ┼ nothing
     │           │        tn ┼ nothing
     │           │      fees ┼ nothing
     │           │       plr ┼ nothing
     │           │       ret ┼ ArithmeticReturn
     │           │           │   ucs ┼ nothing
     │           │           │    lb ┼ Frontier
     │           │           │       │        N ┼ Int64: 15
     │           │           │       │   factor ┼ Int64: 1
     │           │           │       │    bound ┴ LinearBound()
     │           │           │    mu ┴ 20-element Vector{Float64}
     │   retcode ┼ 15-element Vector{OptimisationSuccess}
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │       sol ┼ 15-element Vector{JuMPOptimisationSolution}
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │     model ┼ A JuMP Model
     │           │ ├ solver: Clarabel
     │           │ ├ objective_sense: MIN_SENSE
     │           │ │ └ objective_function_type: QuadExpr
     │           │ ├ num_variables: 22
     │           │ ├ num_constraints: 6
     │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 1
     │           │ │ ├ AffExpr in MOI.GreaterThan{Float64}: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 1
     │           │ │ ├ Vector{AffExpr} in MOI.SecondOrderCone: 1
     │           │ │ └ VariableRef in MOI.Parameter{Float64}: 1
     │           │ └ Names registered in the model
     │           │   └ :G, :bgt, :dev_1, :dev_1_soc, :k, :lw, :obj_expr, :ret, :ret_frontier, :ret_lb, :ret_lb_var, :risk, :risk_vec, :sc, :so, :variance_flag, :variance_risk_1, :w, :w_lb, :w_ub
  fb ┴ nothing

2. Inspecting the inputs

Before trusting an optimisation, look at what fed it. plot_prior summarises the prior in one figure; plot_correlation and plot_mu zoom in on the covariance structure and the expected returns.

julia
plot_prior(pr, rd)

The correlation matrix on its own.

julia
plot_correlation(pr)

3. The allocation

plot_stacked_bar_composition puts candidate books side by side — here minimum-risk versus maximum-ratio — making the difference in concentration immediate.

julia
plot_stacked_bar_composition([res_min, res_ratio], rd;
                             xticks = (1:2, ["Min risk", "Max ratio"]))

4. Where the risk sits

A book can look diversified by weight but be concentrated in risk. plot_risk_contribution decomposes the portfolio risk by asset. The risk measure comes first, and it must be configured for the data: pass factory(Variance(), pr) rather than a bare Variance() (a bare quadratic risk measure has no covariance attached yet — see the findings note). Return-based measures like ConditionalValueatRisk need no factory.

julia
plot_risk_contribution(factory(Variance(), pr), res_min, rd)

5. Realised performance

plot_ptf_cumulative_returns and plot_drawdowns show how the book would have behaved over the sample, and plot_performance_summary collects the headline performance views into one figure.

julia
plot_ptf_cumulative_returns(res_ratio.w, rd)

The drawdown profile.

julia
plot_drawdowns(res_ratio.w, rd)

6. Risk/return geometry

plot_measures scatters portfolios on any pair of risk/return axes, and plot_efficient_frontier draws the frontier itself — the trade-off surface the optimiser traced out.

julia
plot_efficient_frontier(frontier.w, pr; rt = frontier.ret)

7. The dashboard

plot_portfolio_dashboard assembles composition, risk, and performance into a single report — the fastest way to a complete picture of one book.

julia
plot_portfolio_dashboard(res_ratio, rd; r = factory(Variance(), pr))

This is a selection, not the whole catalogue. The same layer also offers network and clustering views (plot_network, plot_dendrogram, plot_clusters, plot_centrality), validation plots (plot_cv_scores, plot_cv_dashboard), cost/turnover plots (plot_turnover), and higher-moment views (plot_coskewness, plot_cokurtosis) — each following the same plot_*(subject, …) convention.


This page was generated using Literate.jl.