The source files can be found in examples/.

Plotting and reporting

PortfolioOptimisers.jl has plotting functions for each stage of an optimisation. They load when you load StatsPlots and GraphRecipes with the package. They plot the inputs, the allocation, the risk of each asset, the performance over the sample, and the portfolios on axes of risk and return. Two dashboards put several of these plots into one figure. This page draws them for one pair of portfolios.

When to reach for this

Reach for these plots to check the inputs before you optimise, to show an allocation to someone else, and to compare candidate portfolios on the same axes. Each plot shows one view. The dashboards, plot_portfolio_dashboard and plot_performance_summary, put several views in one call.

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

1. The portfolios

We compute an empirical prior, a minimum-risk portfolio and a maximum-ratio portfolio to compare, and an efficient frontier for the plots of risk and return.

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 / 252res_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(;                                                                        settings = JuMPReturnsSettings(;                                                                                                       lb = Frontier(;                                                                                                                     N = 15))))))
MeanRiskResult
  jr ┼ JuMPOptimisationResult
     │        pa ┼ ProcessedJuMPOptimiserAttributes
     │           │        pr ┼ LowOrderPrior
     │           │           │         X ┼ 252×20 Matrix{Float64}
     │           │           │       o_X ┼ nothing
     │           │           │        mu ┼ 20-element Vector{Float64}
     │           │           │     sigma ┼ 20×20 Matrix{Float64}
     │           │           │      chol ┼ nothing
     │           │           │         w ┼ nothing
     │           │           │       ens ┼ nothing
     │           │           │       kld ┼ nothing
     │           │           │        ow ┼ nothing
     │           │           │        rr ┼ nothing
     │           │           │       fpr ┼ nothing
     │           │           │     x_src ┼ nothing
     │           │           │   ss_gaps ┼ nothing
     │           │           │        pc ┴ 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
     │           │           │   settings ┼ JuMPReturnsSettings
     │           │           │            │   scale ┼ Float64: 1.0
     │           │           │            │      lb ┼ Frontier
     │           │           │            │         │        N ┼ Int64: 15
     │           │           │            │         │   factor ┼ Int64: 1
     │           │           │            │         │    bound ┴ LinearBound()
     │           │           │            │     rte ┼ Bool: true
     │           │           │            │     fee ┼ Bool: true
     │           │           │            │     mic ┴ Bool: true
     │           │           │        ucs ┼ nothing
     │           │           │         mu ┼ 20-element Vector{Float64}
     │           │           │   mtx_sqrt ┴ EigenFallbackSquareRoot()
     │           │       sca ┼ SumScalariser()
     │           │      imsk ┴ nothing
     │   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, :T, :bgt, :cdev_soc_1, :dev_1, :k, :lw, :obj_expr, :ret, :ret_1, :ret_frontier, :ret_lb_1, :ret_lb_var_1, :ret_vec, :risk, :risk_minimised, :risk_vec, :sc, :so, :variance_flag, :variance_risk_1, :w, :w_gross_ub, :w_lb, :w_ub
   r ┼ Variance
     │    settings ┼ RiskMeasureSettings
     │             │   scale ┼ Float64: 1.0
     │             │      ub ┼ nothing
     │             │     rke ┴ Bool: true
     │       sigma ┼ 20×20 Matrix{Float64}
     │        chol ┼ nothing
     │          rc ┼ nothing
     │         alg ┼ SquaredSOCRiskExpr()
     │    mtx_sqrt ┼ EigenFallbackSquareRoot()
     │   ratio_alg ┼ SquareRootRatio()
     │      corner ┴ nothing
  fb ┴ nothing

2. The inputs

Look at the inputs before you trust an optimisation. plot_prior plots the prior in one figure. plot_correlation plots the correlation matrix alone, and plot_mu plots the expected returns alone.

plot_prior(pr, rd)
Example block output

We also plot the correlation matrix on its own.

plot_correlation(pr)
Example block output

3. The allocation

plot_stacked_bar_composition puts the weights of several portfolios side by side. Here it compares the minimum-risk and the maximum-ratio portfolios, and you can see which of the two puts more weight on fewer assets.

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

4. The risk of each asset

A portfolio can spread its weights over many assets and still take most of its risk from a few. plot_risk_contribution splits the risk of the portfolio by asset. It takes the risk measure as its first argument. A quadratic risk measure needs the covariance of the data, so pass factory(Variance(), pr), not a bare Variance(), which holds no covariance. A measure computed from returns, such as ConditionalValueatRisk, needs no factory.

plot_risk_contribution(factory(Variance(), pr), res_min, rd)
Example block output

5. Performance over the sample

plot_portfolio_cumulative_returns plots the cumulative return of the maximum-ratio portfolio over the sample.

plot_portfolio_cumulative_returns(res_ratio.w, rd)
Example block output

plot_drawdowns plots the drawdowns of the same portfolio.

plot_drawdowns(res_ratio.w, rd)
Example block output

plot_performance_summary puts the main performance plots into one figure. It plots a PerformanceSummaryResult, which performance_summary computes. Call performance_summary when you want the numbers and not the plot, for example to put them in a table, to compare two portfolios, or to test them. It needs no plotting package.

The keyword ppy is the number of periods in one year. Its default of 1 states each figure per period, which is right at any data frequency. Our returns are daily, so ppy = 252 annualises them.

performance_summary(res_ratio, rd; ppy = 252)
PerformanceSummaryResult
          n_periods ┼ Int64: 252
                ppy ┼ Int64: 252
              alpha ┼ Float64: 0.05
           compound ┼ Bool: false
         ann_return ┼ Float64: 0.5035202912659817
     ann_volatility ┼ Float64: 0.19310978475964546
             sharpe ┼ Float64: 2.6074302340126856
      sharpe_stderr ┼ Float64: 0.9985451245993183
            sortino ┼ Float64: 4.290026176056361
             calmar ┼ Float64: 5.07847249328286
       max_drawdown ┼ Float64: -0.099147980407883
               cvar ┼ Float64: -0.023244045086572736
         excess_ret ┼ Float64: NaN
     tracking_error ┼ Float64: NaN
  information_ratio ┼ Float64: NaN
           turnover ┴ Float64: NaN

We draw the same summary as a figure.

plot_performance_summary(res_ratio, rd; ppy = 252)
Example block output

6. Risk and return

plot_measures plots portfolios on any pair of risk and return axes. plot_efficient_frontier plots the efficient frontier, the portfolios with the least risk for each level of return.

plot_efficient_frontier(frontier.w, pr; rt = frontier.ret)
Example block output

7. The dashboard

plot_portfolio_dashboard puts the composition, the risk and the performance of one portfolio into one figure.

plot_portfolio_dashboard(res_ratio, rd; r = factory(Variance(), pr))
Example block output

This page shows some of the plots, not all. The library also plots networks and clusters (plot_network, plot_dendrogram, plot_clusters, plot_centrality), cross-validation (plot_cv_scores, plot_cv_dashboard), turnover (plot_turnover), and the higher moments (plot_coskewness, plot_cokurtosis).


This page was generated using Literate.jl.