The source files can be found in examples/.
Profile: desk, monthly
The second profile is a professional desk rebalancing monthly. The trade-offs invert the retail profile: rebalancing infrequently means each decision can afford real compute and real analysis, and turnover matters far less. The edge here comes from a view and from exploring the whole risk/return trade-off rather than from cost control.
The reasoning, following the strategy decision framework:
- Compute is abundant, decisions are rare — a monthly cadence justifies a richer prior and a full frontier sweep.
- The desk has a view — it encodes a house thesis with an
EntropyPoolingPriorrather than taking the sample moments at face value. - Explore, then choose — instead of one objective, it traces the efficient frontier and selects the risk-adjusted (tangency) book.
- Budget is substantial — an exact
DiscreteAllocationis affordable.
This is the template for a research-driven, lower-frequency book: invest the compute in a better prior and a frontier sweep, pick a point deliberately, and allocate exactly. Turnover and fee control matter less when you trade rarely.
using PortfolioOptimisers, CSV, TimeSeries, DataFrames, PrettyTables, Clarabel, HiGHS, StatsPlots, GraphRecipesresfmt = (v, i, j) -> begin return if j == 1 v else isa(v, AbstractFloat) ? "$(round(v*100, digits=3)) %" : v endend;1. Data and the house view
The desk's thesis: healthcare will outperform energy. It encodes that as an entropy-pooling view, reweighting the empirical scenarios so the prior reflects the conviction (see Entropy Pooling).
X = TimeArray(CSV.File(joinpath(@__DIR__, "..", "SP500.csv.gz")); timestamp = :Date)[(end - 252):end]rd = prices_to_returns(X)prices = vec(values(X)[end, :])sets = UniverseSets(; dict = Dict("nx" => rd.nx, "energy" => ["CVX", "XOM", "RRC"], "healthcare" => ["JNJ", "LLY", "MRK", "PFE", "UNH"]))view_prior = EntropyPoolingPrior(; sets = sets, mu_views = LinearConstraintEstimator(; val = ["healthcare >= energy"]))pr = prior(view_prior, rd)slv = Solver(; name = :clarabel, solver = Clarabel.Optimizer, settings = Dict("verbose" => false), check_sol = (; allow_local = true, allow_almost = true))rf = 4.2 / 100 / 2520.00016666666666666672. The efficient frontier
With compute to spare, the desk traces the whole frontier on the view-tilted prior — minimum-risk books across a sweep of return targets — rather than committing to a single objective up front.
frontier = optimise(MeanRisk(; obj = MinimumRisk(), opt = JuMPOptimiser(; pe = pr, slv = slv, ret = ArithmeticReturn(; settings = JuMPReturnsSettings(; lb = Frontier(; N = 15))))))plot_efficient_frontier(frontier.w, pr; rt = frontier.ret)3. Choosing the book
From the frontier, the desk takes the risk-adjusted optimum — the MaximumRatio (tangency) portfolio on the same view-tilted prior.
desk = optimise(MeanRisk(; obj = MaximumRatio(; rf = rf), opt = JuMPOptimiser(; pe = pr, slv = slv)))pretty_table(DataFrame("Asset" => rd.nx, "Tangency weight" => desk.w); formatters = [resfmt], title = "Desk monthly — risk-adjusted optimum on the view prior")Desk monthly — risk-adjusted optimum on the view prior
┌────────┬─────────────────┐
│ Asset │ Tangency weight │
│ String │ Float64 │
├────────┼─────────────────┤
│ AAPL │ 0.0 % │
│ AMD │ 0.0 % │
│ BAC │ 0.0 % │
│ BBY │ 0.0 % │
│ CVX │ 0.0 % │
│ GE │ 0.0 % │
│ HD │ 0.0 % │
│ JNJ │ 0.0 % │
│ JPM │ 0.0 % │
│ KO │ 0.0 % │
│ LLY │ 0.002 % │
│ MRK │ 76.988 % │
│ MSFT │ 0.0 % │
│ PEP │ 0.0 % │
│ PFE │ 0.0 % │
│ PG │ 0.0 % │
│ RRC │ 0.0 % │
│ UNH │ 0.0 % │
│ WMT │ 0.0 % │
│ XOM │ 23.008 % │
└────────┴─────────────────┘4. Exact finite allocation
On a $500,000 book the rounding is small but the desk wants the provably-best whole-share book, so it uses DiscreteAllocation with a MIP solver (HiGHS).
mip_slv = Solver(; name = :highs, solver = HiGHS.Optimizer, settings = Dict("log_to_console" => false))alloc = optimise(DiscreteAllocation(; slv = mip_slv), FiniteAllocationInput(; w = desk.w, prices = prices, cash = 500_000.0))invested = sum(alloc.shares .* prices)pretty_table(DataFrame("Asset" => rd.nx, "Target" => desk.w, "Shares" => round.(Int, alloc.shares), "Realised" => alloc.w); formatters = [resfmt], title = "\$500,000 allocated — invested \```math(round(Int, invested)), cash left \```(round(alloc.cash, digits = 2))")$500,000 allocated — invested ```math(round(Int, invested)), cash left ```(round(alloc.cash, digits = 2))
┌────────┬──────────┬────────┬──────────┐
│ Asset │ Target │ Shares │ Realised │
│ String │ Float64 │ Int64 │ Float64 │
├────────┼──────────┼────────┼──────────┤
│ AAPL │ 0.0 % │ 0 │ 0.0 % │
│ AMD │ 0.0 % │ 0 │ 0.0 % │
│ BAC │ 0.0 % │ 0 │ 0.0 % │
│ BBY │ 0.0 % │ 0 │ 0.0 % │
│ CVX │ 0.0 % │ 0 │ 0.0 % │
│ GE │ 0.0 % │ 0 │ 0.0 % │
│ HD │ 0.0 % │ 0 │ 0.0 % │
│ JNJ │ 0.0 % │ 0 │ 0.0 % │
│ JPM │ 0.0 % │ 0 │ 0.0 % │
│ KO │ 0.0 % │ 1 │ 0.013 % │
│ LLY │ 0.002 % │ 0 │ 0.0 % │
│ MRK │ 76.988 % │ 3512 │ 76.972 % │
│ MSFT │ 0.0 % │ 0 │ 0.0 % │
│ PEP │ 0.0 % │ 0 │ 0.0 % │
│ PFE │ 0.0 % │ 0 │ 0.0 % │
│ PG │ 0.0 % │ 0 │ 0.0 % │
│ RRC │ 0.0 % │ 1 │ 0.005 % │
│ UNH │ 0.0 % │ 0 │ 0.0 % │
│ WMT │ 0.0 % │ 0 │ 0.0 % │
│ XOM │ 23.008 % │ 1079 │ 23.011 % │
└────────┴──────────┴────────┴──────────┘5. The book
plot_stacked_bar_composition([desk], rd; xticks = (1:1, ["Desk monthly"]))This page was generated using Literate.jl.