diff --git a/.github/workflows/Aqua.yml b/.github/workflows/Aqua.yml new file mode 100644 index 000000000..c46ecc864 --- /dev/null +++ b/.github/workflows/Aqua.yml @@ -0,0 +1,33 @@ +name: Aqua + +on: + push: + branches: + - main + tags: ["*"] + workflow_dispatch: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: julia-actions/setup-julia@latest + with: + version: '1' + - uses: actions/checkout@v5 + - name: Aqua + shell: julia --color=yes {0} + run: | + using Pkg + Pkg.add(PackageSpec(name="Aqua")) + Pkg.develop(PackageSpec(path=pwd())) + using PortfolioOptimisers, Aqua + Aqua.test_ambiguities(PortfolioOptimisers) + Aqua.test_unbound_args(PortfolioOptimisers) + Aqua.test_undefined_exports(PortfolioOptimisers) + Aqua.test_project_extras(PortfolioOptimisers) + Aqua.test_stale_deps(PortfolioOptimisers) + Aqua.test_piracies(PortfolioOptimisers) + Aqua.test_persistent_tasks(PortfolioOptimisers) + Aqua.test_deps_compat(PortfolioOptimisers) diff --git a/.github/workflows/Lint.yml b/.github/workflows/Lint.yml index 9560e1332..de14ddc48 100644 --- a/.github/workflows/Lint.yml +++ b/.github/workflows/Lint.yml @@ -15,40 +15,63 @@ concurrency: jobs: - lint: - name: Linting + # lint: + # name: Linting + # runs-on: ubuntu-latest + # steps: + # - name: Clone + # uses: actions/checkout@v5 + # - name: Setup Julia + # uses: julia-actions/setup-julia@v2 + # with: + # version: "1" + # - name: Use Julia cache + # uses: julia-actions/cache@v2 + # - name: Build package (required for ExplicitImports) + # uses: julia-actions/julia-buildpkg@v1 + # - name: Install Julia packages + # run: julia -e 'using Pkg; pkg"add ExplicitImports, JuliaFormatter"' + # - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807 + # run: touch requirements.txt + # - name: Setup Python + # uses: actions/setup-python@v6 + # with: + # cache: "pip" + # python-version: "3.11" + # - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807 + # run: rm requirements.txt + # - name: Cache pre-commit + # uses: actions/cache@v4 + # with: + # path: ~/.cache/pre-commit + # key: ${{ runner.os }}-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} + # - name: Install pre-commit + # run: pip install pre-commit + # - name: Run pre-commit + # run: SKIP=no-commit-to-branch pre-commit run -a + build: + name: Format check runs-on: ubuntu-latest steps: - - name: Clone - uses: actions/checkout@v5 - - name: Setup Julia - uses: julia-actions/setup-julia@v2 + - uses: julia-actions/setup-julia@latest with: - version: "1" - - name: Use Julia cache - uses: julia-actions/cache@v2 - - name: Build package (required for ExplicitImports) - uses: julia-actions/julia-buildpkg@v1 - - name: Install Julia packages - run: julia -e 'using Pkg; pkg"add ExplicitImports, JuliaFormatter"' - - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807 - run: touch requirements.txt - - name: Setup Python - uses: actions/setup-python@v6 - with: - cache: "pip" - python-version: "3.11" - - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807 - run: rm requirements.txt - - name: Cache pre-commit - uses: actions/cache@v4 - with: - path: ~/.cache/pre-commit - key: ${{ runner.os }}-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} - - name: Install pre-commit - run: pip install pre-commit - - name: Run pre-commit - run: SKIP=no-commit-to-branch pre-commit run -a + version: '1' + - uses: actions/checkout@v4 + - name: Format check + shell: julia --color=yes {0} + run: | + using Pkg + # If you update the version, also update the style guide docs. + Pkg.add(PackageSpec(name="JuliaFormatter", version="2")) + using JuliaFormatter + format("."; verbose = true) + out = String(read(Cmd(`git diff`))) + if isempty(out) + exit(0) + end + @error "Some files have not been formatted !!!" + write(stdout, out) + exit(1) link-checker: name: Link checker runs-on: ubuntu-latest diff --git a/LICENSE b/LICENSE index 3467d6884..ebecbc48e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Daniel Celis Garza +Copyright (c) 2025 Daniel Celis Garza Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Project.toml b/Project.toml index 46d52c632..4dde01ea7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PortfolioOptimisers" uuid = "e0036ec9-05e5-505d-a6a9-07af41c94861" authors = ["Daniel Celis Garza "] -version = "0.1.0" +version = "0.2.0" [deps] ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197" diff --git a/README.md b/README.md index 68268e28e..40ae7e8bd 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) [![All Contributors](https://img.shields.io/github/all-contributors/dcelisgarza/PortfolioOptimisers.jl?labelColor=5e1ec7&color=c0ffee&style=flat-square)](#contributors) [![BestieTemplate](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/JuliaBesties/BestieTemplate.jl/main/docs/src/assets/badge.json)](https://github.com/JuliaBesties/BestieTemplate.jl) +[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) ## How to Cite diff --git a/docs/src/api/13_Prior/01_BasePrior.md b/docs/src/api/13_Prior/01_BasePrior.md new file mode 100644 index 000000000..be6a90b97 --- /dev/null +++ b/docs/src/api/13_Prior/01_BasePrior.md @@ -0,0 +1,19 @@ +# Base Prior + +```@docs +LowOrderPrior +HighOrderPrior +prior(pr::PortfolioOptimisers.AbstractPriorEstimator, rd::ReturnsResult; kwargs...) +prior(pr::PortfolioOptimisers.AbstractPriorResult, args...; kwargs...) +clusterise(cle::ClusteringEstimator, pr::PortfolioOptimisers.AbstractPriorResult; kwargs...) +PortfolioOptimisers.AbstractPriorEstimator +PortfolioOptimisers.AbstractLowOrderPriorEstimator +PortfolioOptimisers.AbstractLowOrderPriorEstimator_A +PortfolioOptimisers.AbstractLowOrderPriorEstimator_F +PortfolioOptimisers.AbstractLowOrderPriorEstimator_AF +PortfolioOptimisers.AbstractLowOrderPriorEstimator_A_AF +PortfolioOptimisers.AbstractLowOrderPriorEstimator_F_AF +PortfolioOptimisers.AbstractLowOrderPriorEstimator_A_F_AF +PortfolioOptimisers.AbstractHighOrderPriorEstimator +PortfolioOptimisers.AbstractPriorResult +``` diff --git a/docs/src/api/13_Prior/02_EmpiricalPrior.md b/docs/src/api/13_Prior/02_EmpiricalPrior.md new file mode 100644 index 000000000..91c498189 --- /dev/null +++ b/docs/src/api/13_Prior/02_EmpiricalPrior.md @@ -0,0 +1,7 @@ +# Empirical Prior + +```@docs +EmpiricalPrior +prior(pe::EmpiricalPrior{<:Any, <:Any, Nothing}, X::AbstractMatrix, args...; dims::Int = 1, + kwargs...) +``` diff --git a/docs/src/api/13_Prior/03_FactorPrior.md b/docs/src/api/13_Prior/03_FactorPrior.md new file mode 100644 index 000000000..98c2a00bf --- /dev/null +++ b/docs/src/api/13_Prior/03_FactorPrior.md @@ -0,0 +1,7 @@ +# Factor Prior + +```@docs +FactorPrior +prior(pe::FactorPrior, X::AbstractMatrix, F::AbstractMatrix; dims::Int = 1, + kwargs...) +``` diff --git a/docs/src/api/13_Prior/04_HighOrderPrior.md b/docs/src/api/13_Prior/04_HighOrderPrior.md new file mode 100644 index 000000000..351c8b185 --- /dev/null +++ b/docs/src/api/13_Prior/04_HighOrderPrior.md @@ -0,0 +1,9 @@ +# High Order Prior + +```@docs +HighOrderPriorEstimator +prior(pe::HighOrderPriorEstimator, X::AbstractMatrix, + F::Union{Nothing, <:AbstractMatrix} = nothing; dims::Int = 1, kwargs...) +PortfolioOptimisers.block_vec_pq +PortfolioOptimisers.dup_elim_sum_matrices +``` diff --git a/docs/src/api/13_Prior/10_EntropyPoolingPrior.md b/docs/src/api/13_Prior/10_EntropyPoolingPrior.md index b130c60be..c9cff6023 100644 --- a/docs/src/api/13_Prior/10_EntropyPoolingPrior.md +++ b/docs/src/api/13_Prior/10_EntropyPoolingPrior.md @@ -1,4 +1,4 @@ -# Prior +# Entropy Pooling ```@docs PortfolioOptimisers.replace_prior_views diff --git a/docs/src/api/13_Prior/1_BasePrior.md b/docs/src/api/13_Prior/1_BasePrior.md deleted file mode 100644 index 648ca4683..000000000 --- a/docs/src/api/13_Prior/1_BasePrior.md +++ /dev/null @@ -1,5 +0,0 @@ -# Base Prior - -```@docs -PortfolioOptimisers.AbstractPriorResult -``` diff --git a/examples/1_Getting_Started.ipynb b/examples/1_Getting_Started.ipynb index d1e865ccb..093bcae4a 100644 --- a/examples/1_Getting_Started.ipynb +++ b/examples/1_Getting_Started.ipynb @@ -188,7 +188,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "MeanRisk\n opt | JuMPOptimiser\n | pe | EmpiricalPrior\n | | ce | PortfolioOptimisersCovariance\n | | | ce | Covariance\n | | | | me | SimpleExpectedReturns\n | | | | | w | nothing\n | | | | ce | GeneralWeightedCovariance\n | | | | | ce | SimpleCovariance: SimpleCovariance(true)\n | | | | | w | nothing\n | | | | alg | Full()\n | | | mp | DefaultMatrixProcessing\n | | | | pdm | Posdef\n | | | | | alg | UnionAll: NearestCorrelationMatrix.Newton\n | | | | denoise | nothing\n | | | | detone | nothing\n | | | | alg | nothing\n | | me | SimpleExpectedReturns\n | | | w | nothing\n | | horizon | nothing\n | slv | Solver\n | | name | Symbol: :clarabel1\n | | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | | settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n | | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | | add_bridges | Bool: true\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | Variance\n | settings | RiskMeasureSettings\n | | scale | Float64: 1.0\n | | ub | nothing\n | | rke | Bool: true\n | sigma | nothing\n | rc | nothing\n | alg | SOCRiskExpr()\n obj | MinimumRisk()\n wi | nothing\n fallback | nothing\n" + "text/plain": "MeanRisk\n opt | JuMPOptimiser\n | pe | EmpiricalPrior\n | | ce | PortfolioOptimisersCovariance\n | | | ce | Covariance\n | | | | me | SimpleExpectedReturns\n | | | | | w | nothing\n | | | | ce | GeneralWeightedCovariance\n | | | | | ce | SimpleCovariance: SimpleCovariance(true)\n | | | | | w | nothing\n | | | | alg | Full()\n | | | mp | DefaultMatrixProcessing\n | | | | pdm | Posdef\n | | | | | alg | UnionAll: NearestCorrelationMatrix.Newton\n | | | | denoise | nothing\n | | | | detone | nothing\n | | | | alg | nothing\n | | me | SimpleExpectedReturns\n | | | w | nothing\n | | horizon | nothing\n | slv | Solver\n | | name | Symbol: :clarabel1\n | | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | | settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n | | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | | add_bridges | Bool: true\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | plg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | Variance\n | settings | RiskMeasureSettings\n | | scale | Float64: 1.0\n | | ub | nothing\n | | rke | Bool: true\n | sigma | nothing\n | rc | nothing\n | alg | SOCRiskExpr()\n obj | MinimumRisk()\n wi | nothing\n fallback | nothing\n" }, "metadata": {}, "execution_count": 7 @@ -217,7 +217,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "JuMPOptimisation\n oe | DataType: MeanRisk\n pa | ProcessedJuMPOptimiserAttributes\n | pr | LowOrderPrior\n | | X | 252×20 Matrix{Float64}\n | | mu | 20-element Vector{Float64}\n | | sigma | 20×20 Matrix{Float64}\n | | chol | nothing\n | | w | nothing\n | | ens | nothing\n | | kld | nothing\n | | ow | nothing\n | | rr | nothing\n | | f_mu | nothing\n | | f_sigma | nothing\n | | f_w | nothing\n | wb | WeightBounds\n | | lb | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | | ub | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n sol | PortfolioOptimisers.JuMPOptimisationSolution\n | w | 20-element Vector{Float64}\n model | A JuMP Model\n | ├ solver: Clarabel\n | ├ objective_sense: MIN_SENSE\n | │ └ objective_function_type: JuMP.QuadExpr\n | ├ num_variables: 21\n | ├ num_constraints: 4\n | │ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonnegatives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonpositives: 1\n | │ └ Vector{JuMP.AffExpr} in MOI.SecondOrderCone: 1\n | └ Names registered in the model\n | └ :G, :bgt, :dev_1, :dev_1_soc, :k, :lw, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :variance_flag, :variance_risk_1, :w, :w_lb, :w_ub\n" + "text/plain": "JuMPOptimisation\n oe | DataType: MeanRisk\n pa | ProcessedJuMPOptimiserAttributes\n | pr | LowOrderPrior\n | | X | 252×20 Matrix{Float64}\n | | mu | 20-element Vector{Float64}\n | | sigma | 20×20 Matrix{Float64}\n | | chol | nothing\n | | w | nothing\n | | ens | nothing\n | | kld | nothing\n | | ow | nothing\n | | rr | nothing\n | | f_mu | nothing\n | | f_sigma | nothing\n | | f_w | nothing\n | wb | WeightBounds\n | | lb | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | | ub | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | plg | nothing\n | tn | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n sol | PortfolioOptimisers.JuMPOptimisationSolution\n | w | 20-element Vector{Float64}\n model | A JuMP Model\n | ├ solver: Clarabel\n | ├ objective_sense: MIN_SENSE\n | │ └ objective_function_type: JuMP.QuadExpr\n | ├ num_variables: 21\n | ├ num_constraints: 4\n | │ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonnegatives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonpositives: 1\n | │ └ Vector{JuMP.AffExpr} in MOI.SecondOrderCone: 1\n | └ Names registered in the model\n | └ :G, :bgt, :dev_1, :dev_1_soc, :k, :lw, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :variance_flag, :variance_risk_1, :w, :w_lb, :w_ub\n" }, "metadata": {}, "execution_count": 8 diff --git a/examples/2_Mean_Risk_Objectives.ipynb b/examples/2_Mean_Risk_Objectives.ipynb index 685c4befb..4ed782a35 100644 --- a/examples/2_Mean_Risk_Objectives.ipynb +++ b/examples/2_Mean_Risk_Objectives.ipynb @@ -177,7 +177,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "JuMPOptimiser\n pe | LowOrderPrior\n | X | 252×20 Matrix{Float64}\n | mu | 20-element Vector{Float64}\n | sigma | 20×20 Matrix{Float64}\n | chol | nothing\n | w | nothing\n | ens | nothing\n | kld | nothing\n | ow | nothing\n | rr | nothing\n | f_mu | nothing\n | f_sigma | nothing\n | f_w | nothing\n slv | Solver\n | name | Symbol: :clarabel1\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n wb | WeightBounds\n | lb | Float64: 0.0\n | ub | Float64: 1.0\n bgt | Float64: 1.0\n sbgt | nothing\n lt | nothing\n st | nothing\n lcs | nothing\n lcm | nothing\n cent | nothing\n gcard | nothing\n sgcard | nothing\n smtx | nothing\n sgmtx | nothing\n slt | nothing\n sst | nothing\n sglt | nothing\n sgst | nothing\n sets | nothing\n nplg | nothing\n cplg | nothing\n tn | nothing\n te | nothing\n fees | nothing\n ret | ArithmeticReturn\n | ucs | nothing\n | lb | nothing\n sce | SumScalariser: SumScalariser()\n ccnt | nothing\n cobj | nothing\n sc | Int64: 1\n so | Int64: 1\n card | nothing\n scard | nothing\n nea | nothing\n l1 | nothing\n l2 | nothing\n ss | nothing\n strict | Bool: false\n" + "text/plain": "JuMPOptimiser\n pe | LowOrderPrior\n | X | 252×20 Matrix{Float64}\n | mu | 20-element Vector{Float64}\n | sigma | 20×20 Matrix{Float64}\n | chol | nothing\n | w | nothing\n | ens | nothing\n | kld | nothing\n | ow | nothing\n | rr | nothing\n | f_mu | nothing\n | f_sigma | nothing\n | f_w | nothing\n slv | Solver\n | name | Symbol: :clarabel1\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n wb | WeightBounds\n | lb | Float64: 0.0\n | ub | Float64: 1.0\n bgt | Float64: 1.0\n sbgt | nothing\n lt | nothing\n st | nothing\n lcs | nothing\n lcm | nothing\n cent | nothing\n gcard | nothing\n sgcard | nothing\n smtx | nothing\n sgmtx | nothing\n slt | nothing\n sst | nothing\n sglt | nothing\n sgst | nothing\n sets | nothing\n plg | nothing\n tn | nothing\n te | nothing\n fees | nothing\n ret | ArithmeticReturn\n | ucs | nothing\n | lb | nothing\n sce | SumScalariser: SumScalariser()\n ccnt | nothing\n cobj | nothing\n sc | Int64: 1\n so | Int64: 1\n card | nothing\n scard | nothing\n nea | nothing\n l1 | nothing\n l2 | nothing\n ss | nothing\n strict | Bool: false\n" }, "metadata": {}, "execution_count": 6 @@ -202,7 +202,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "MeanRisk\n opt | JuMPOptimiser\n | pe | LowOrderPrior\n | | X | 252×20 Matrix{Float64}\n | | mu | 20-element Vector{Float64}\n | | sigma | 20×20 Matrix{Float64}\n | | chol | nothing\n | | w | nothing\n | | ens | nothing\n | | kld | nothing\n | | ow | nothing\n | | rr | nothing\n | | f_mu | nothing\n | | f_sigma | nothing\n | | f_w | nothing\n | slv | Solver\n | | name | Symbol: :clarabel1\n | | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | | settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n | | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | | add_bridges | Bool: true\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | LowOrderMoment\n | settings | RiskMeasureSettings\n | | scale | Float64: 1.0\n | | ub | nothing\n | | rke | Bool: true\n | w | nothing\n | mu | nothing\n | alg | LowOrderDeviation\n | | ve | SimpleVariance\n | | | me | nothing\n | | | w | nothing\n | | | corrected | Bool: true\n | | alg | SecondLowerMoment\n | | | alg | SqrtRiskExpr()\n obj | MaximumReturn()\n wi | nothing\n fallback | nothing\n" + "text/plain": "MeanRisk\n opt | JuMPOptimiser\n | pe | LowOrderPrior\n | | X | 252×20 Matrix{Float64}\n | | mu | 20-element Vector{Float64}\n | | sigma | 20×20 Matrix{Float64}\n | | chol | nothing\n | | w | nothing\n | | ens | nothing\n | | kld | nothing\n | | ow | nothing\n | | rr | nothing\n | | f_mu | nothing\n | | f_sigma | nothing\n | | f_w | nothing\n | slv | Solver\n | | name | Symbol: :clarabel1\n | | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | | settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n | | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | | add_bridges | Bool: true\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | plg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | LowOrderMoment\n | settings | RiskMeasureSettings\n | | scale | Float64: 1.0\n | | ub | nothing\n | | rke | Bool: true\n | w | nothing\n | mu | nothing\n | alg | LowOrderDeviation\n | | ve | SimpleVariance\n | | | me | nothing\n | | | w | nothing\n | | | corrected | Bool: true\n | | alg | SecondLowerMoment\n | | | alg | SqrtRiskExpr()\n obj | MaximumReturn()\n wi | nothing\n fallback | nothing\n" }, "metadata": {}, "execution_count": 7 diff --git a/examples/3_Efficient_Frontier.ipynb b/examples/3_Efficient_Frontier.ipynb index d43770c78..9967b09c1 100644 --- a/examples/3_Efficient_Frontier.ipynb +++ b/examples/3_Efficient_Frontier.ipynb @@ -156,7 +156,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "JuMPOptimiser\n pe | LowOrderPrior\n | X | 252×20 Matrix{Float64}\n | mu | 20-element Vector{Float64}\n | sigma | 20×20 Matrix{Float64}\n | chol | nothing\n | w | nothing\n | ens | nothing\n | kld | nothing\n | ow | nothing\n | rr | nothing\n | f_mu | nothing\n | f_sigma | nothing\n | f_w | nothing\n slv | Vector{Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3}: Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3[Solver\n name | Symbol: :clarabel1\n solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n add_bridges | Bool: true\n, Solver\n name | Symbol: :clarabel2\n solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n settings | Dict{String, Real}: Dict{String, Real}(\"verbose\" => false, \"max_step_fraction\" => 0.75)\n check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n add_bridges | Bool: true\n]\n wb | WeightBounds\n | lb | Float64: 0.0\n | ub | Float64: 1.0\n bgt | Float64: 1.0\n sbgt | nothing\n lt | nothing\n st | nothing\n lcs | nothing\n lcm | nothing\n cent | nothing\n gcard | nothing\n sgcard | nothing\n smtx | nothing\n sgmtx | nothing\n slt | nothing\n sst | nothing\n sglt | nothing\n sgst | nothing\n sets | nothing\n nplg | nothing\n cplg | nothing\n tn | nothing\n te | nothing\n fees | nothing\n ret | ArithmeticReturn\n | ucs | nothing\n | lb | Frontier\n | | N | Int64: 30\n | | factor | Int64: 1\n | | flag | Bool: true\n sce | SumScalariser: SumScalariser()\n ccnt | nothing\n cobj | nothing\n sc | Int64: 1\n so | Int64: 1\n card | nothing\n scard | nothing\n nea | nothing\n l1 | nothing\n l2 | nothing\n ss | nothing\n strict | Bool: false\n" + "text/plain": "JuMPOptimiser\n pe | LowOrderPrior\n | X | 252×20 Matrix{Float64}\n | mu | 20-element Vector{Float64}\n | sigma | 20×20 Matrix{Float64}\n | chol | nothing\n | w | nothing\n | ens | nothing\n | kld | nothing\n | ow | nothing\n | rr | nothing\n | f_mu | nothing\n | f_sigma | nothing\n | f_w | nothing\n slv | Vector{Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3}: Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3[Solver\n name | Symbol: :clarabel1\n solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n add_bridges | Bool: true\n, Solver\n name | Symbol: :clarabel2\n solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n settings | Dict{String, Real}: Dict{String, Real}(\"verbose\" => false, \"max_step_fraction\" => 0.75)\n check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n add_bridges | Bool: true\n]\n wb | WeightBounds\n | lb | Float64: 0.0\n | ub | Float64: 1.0\n bgt | Float64: 1.0\n sbgt | nothing\n lt | nothing\n st | nothing\n lcs | nothing\n lcm | nothing\n cent | nothing\n gcard | nothing\n sgcard | nothing\n smtx | nothing\n sgmtx | nothing\n slt | nothing\n sst | nothing\n sglt | nothing\n sgst | nothing\n sets | nothing\n plg | nothing\n tn | nothing\n te | nothing\n fees | nothing\n ret | ArithmeticReturn\n | ucs | nothing\n | lb | Frontier\n | | N | Int64: 30\n | | factor | Int64: 1\n | | flag | Bool: true\n sce | SumScalariser: SumScalariser()\n ccnt | nothing\n cobj | nothing\n sc | Int64: 1\n so | Int64: 1\n card | nothing\n scard | nothing\n nea | nothing\n l1 | nothing\n l2 | nothing\n ss | nothing\n strict | Bool: false\n" }, "metadata": {}, "execution_count": 5 @@ -181,7 +181,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "JuMPOptimisation\n oe | DataType: MeanRisk\n pa | ProcessedJuMPOptimiserAttributes\n | pr | LowOrderPrior\n | | X | 252×20 Matrix{Float64}\n | | mu | 20-element Vector{Float64}\n | | sigma | 20×20 Matrix{Float64}\n | | chol | nothing\n | | w | nothing\n | | ens | nothing\n | | kld | nothing\n | | ow | nothing\n | | rr | nothing\n | | f_mu | nothing\n | | f_sigma | nothing\n | | f_w | nothing\n | wb | WeightBounds\n | | lb | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | | ub | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | Frontier\n | | | N | Int64: 30\n | | | factor | Int64: 1\n | | | flag | Bool: true\n retcode | 30-element Vector{PortfolioOptimisers.OptimisationReturnCode}\n sol | 30-element Vector{PortfolioOptimisers.JuMPOptimisationSolution}\n model | A JuMP Model\n | ├ solver: Clarabel\n | ├ objective_sense: MIN_SENSE\n | │ └ objective_function_type: JuMP.AffExpr\n | ├ num_variables: 273\n | ├ num_constraints: 257\n | │ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 1\n | │ ├ JuMP.AffExpr in MOI.GreaterThan{Float64}: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonnegatives: 2\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonpositives: 1\n | │ └ JuMP.VariableRef in MOI.GreaterThan{Float64}: 252\n | └ Names registered in the model\n | └ :X, :bgt, :ccvar_1, :cvar_risk_1, :k, :lw, :net_X, :obj_expr, :ret, :ret_frontier, :ret_lb, :risk, :risk_vec, :sc, :so, :var_1, :w, :w_lb, :w_ub, :z_cvar_1\n" + "text/plain": "JuMPOptimisation\n oe | DataType: MeanRisk\n pa | ProcessedJuMPOptimiserAttributes\n | pr | LowOrderPrior\n | | X | 252×20 Matrix{Float64}\n | | mu | 20-element Vector{Float64}\n | | sigma | 20×20 Matrix{Float64}\n | | chol | nothing\n | | w | nothing\n | | ens | nothing\n | | kld | nothing\n | | ow | nothing\n | | rr | nothing\n | | f_mu | nothing\n | | f_sigma | nothing\n | | f_w | nothing\n | wb | WeightBounds\n | | lb | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | | ub | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | plg | nothing\n | tn | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | Frontier\n | | | N | Int64: 30\n | | | factor | Int64: 1\n | | | flag | Bool: true\n retcode | 30-element Vector{PortfolioOptimisers.OptimisationReturnCode}\n sol | 30-element Vector{PortfolioOptimisers.JuMPOptimisationSolution}\n model | A JuMP Model\n | ├ solver: Clarabel\n | ├ objective_sense: MIN_SENSE\n | │ └ objective_function_type: JuMP.AffExpr\n | ├ num_variables: 273\n | ├ num_constraints: 257\n | │ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 1\n | │ ├ JuMP.AffExpr in MOI.GreaterThan{Float64}: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonnegatives: 2\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonpositives: 1\n | │ └ JuMP.VariableRef in MOI.GreaterThan{Float64}: 252\n | └ Names registered in the model\n | └ :X, :bgt, :ccvar_1, :cvar_risk_1, :k, :lw, :net_X, :obj_expr, :ret, :ret_frontier, :ret_lb, :risk, :risk_vec, :sc, :so, :var_1, :w, :w_lb, :w_ub, :z_cvar_1\n" }, "metadata": {}, "execution_count": 6 @@ -292,177 +292,177 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" ] }, "metadata": {}, @@ -499,82 +499,82 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" ] }, "metadata": {}, @@ -676,80 +676,80 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" ] }, "metadata": {}, diff --git a/examples/4_Pareto_Surface.ipynb b/examples/4_Pareto_Surface.ipynb index 0429cff5a..1486b6091 100644 --- a/examples/4_Pareto_Surface.ipynb +++ b/examples/4_Pareto_Surface.ipynb @@ -239,7 +239,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "NearOptimalCentering\n opt | JuMPOptimiser\n | pe | HighOrderPrior\n | | pr | LowOrderPrior\n | | | X | 252×20 Matrix{Float64}\n | | | mu | 20-element Vector{Float64}\n | | | sigma | 20×20 Matrix{Float64}\n | | | chol | nothing\n | | | w | nothing\n | | | ens | nothing\n | | | kld | nothing\n | | | ow | nothing\n | | | rr | nothing\n | | | f_mu | nothing\n | | | f_sigma | nothing\n | | | f_w | nothing\n | | kt | 400×400 Matrix{Float64}\n | | L2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | S2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | sk | 20×400 Matrix{Float64}\n | | V | 20×20 Matrix{Float64}\n | | skmp | NonPositiveDefiniteMatrixProcessing\n | | | denoise | nothing\n | | | detone | nothing\n | | | alg | nothing\n | slv | 7-element Vector{Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3}\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | SquareRootKurtosis\n | settings | RiskMeasureSettings\n | | scale | Float64: 1.0\n | | ub | nothing\n | | rke | Bool: true\n | w | nothing\n | mu | nothing\n | kt | nothing\n | N | nothing\n | alg | Full()\n obj | MaximumRatio\n | rf | Float64: 0.0001666666666666667\n | ohf | nothing\n bins | nothing\n w_min | nothing\n w_min_ini | nothing\n w_opt | nothing\n w_opt_ini | nothing\n w_max | nothing\n w_max_ini | nothing\n ucs_flag | Bool: true\n alg | UnconstrainedNearOptimalCentering()\n fallback | nothing\n" + "text/plain": "NearOptimalCentering\n opt | JuMPOptimiser\n | pe | HighOrderPrior\n | | pr | LowOrderPrior\n | | | X | 252×20 Matrix{Float64}\n | | | mu | 20-element Vector{Float64}\n | | | sigma | 20×20 Matrix{Float64}\n | | | chol | nothing\n | | | w | nothing\n | | | ens | nothing\n | | | kld | nothing\n | | | ow | nothing\n | | | rr | nothing\n | | | f_mu | nothing\n | | | f_sigma | nothing\n | | | f_w | nothing\n | | kt | 400×400 Matrix{Float64}\n | | L2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | S2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | sk | 20×400 Matrix{Float64}\n | | V | 20×20 Matrix{Float64}\n | | skmp | NonPositiveDefiniteMatrixProcessing\n | | | denoise | nothing\n | | | detone | nothing\n | | | alg | nothing\n | slv | 7-element Vector{Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3}\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | plg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | SquareRootKurtosis\n | settings | RiskMeasureSettings\n | | scale | Float64: 1.0\n | | ub | nothing\n | | rke | Bool: true\n | w | nothing\n | mu | nothing\n | kt | nothing\n | N | nothing\n | alg | Full()\n obj | MaximumRatio\n | rf | Float64: 0.0001666666666666667\n | ohf | nothing\n bins | nothing\n w_min | nothing\n w_min_ini | nothing\n w_opt | nothing\n w_opt_ini | nothing\n w_max | nothing\n w_max_ini | nothing\n ucs_flag | Bool: true\n alg | UnconstrainedNearOptimalCentering()\n fallback | nothing\n" }, "metadata": {}, "execution_count": 7 @@ -271,7 +271,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "NearOptimalCenteringOptimisation\n oe | DataType: NearOptimalCentering\n pa | ProcessedJuMPOptimiserAttributes\n | pr | HighOrderPrior\n | | pr | LowOrderPrior\n | | | X | 252×20 Matrix{Float64}\n | | | mu | 20-element Vector{Float64}\n | | | sigma | 20×20 Matrix{Float64}\n | | | chol | nothing\n | | | w | nothing\n | | | ens | nothing\n | | | kld | nothing\n | | | ow | nothing\n | | | rr | nothing\n | | | f_mu | nothing\n | | | f_sigma | nothing\n | | | f_w | nothing\n | | kt | 400×400 Matrix{Float64}\n | | L2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | S2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | sk | 20×400 Matrix{Float64}\n | | V | 20×20 Matrix{Float64}\n | | skmp | NonPositiveDefiniteMatrixProcessing\n | | | denoise | nothing\n | | | detone | nothing\n | | | alg | nothing\n | wb | WeightBounds\n | | lb | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | | ub | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n w_min_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n w_opt_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n w_max_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n noc_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}(:clarabel1 => Dict{Symbol, Any}(:settings => Dict{String, Bool}(\"verbose\" => 0), :err => solution_summary(; result = 1, verbose = false)\n | ├ solver_name : Clarabel\n | ├ Termination\n | │ ├ termination_status : SLOW_PROGRESS\n | │ ├ result_count : 1\n | │ └ raw_status : INSUFFICIENT_PROGRESS\n | ├ Solution (result = 1)\n | │ ├ primal_status : OTHER_RESULT_STATUS\n | │ ├ dual_status : OTHER_RESULT_STATUS\n | │ ├ objective_value : 9.29402e+01\n | │ └ dual_objective_value : 9.29052e+01\n | └ Work counters\n | ├ solve_time (sec) : 3.21330e-01\n | └ barrier_iterations : 35))\n retcode | OptimisationSuccess\n | res | nothing\n sol | PortfolioOptimisers.JuMPOptimisationSolution\n | w | 20-element Vector{Float64}\n model | A JuMP Model\n | ├ solver: Clarabel\n | ├ objective_sense: MIN_SENSE\n | │ └ objective_function_type: JuMP.AffExpr\n | ├ num_variables: 273\n | ├ num_constraints: 47\n | │ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonnegatives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonpositives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.SecondOrderCone: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.ExponentialCone: 42\n | │ └ Vector{JuMP.AffExpr} in MOI.PositiveSemidefiniteConeSquare: 1\n | └ Names registered in the model\n | └ :M_PSD, :W, :W_M, :bgt, :ckurt_soc_1, :clog_delta_w, :clog_ret, :clog_risk, :clog_w, :k, :log_delta_w, :log_ret, :log_risk, :log_w, :lw, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :sqrt_kurtosis_risk_1, :w, :w_lb, :w_ub, :zkurt_1\n" + "text/plain": "NearOptimalCenteringOptimisation\n oe | DataType: NearOptimalCentering\n pa | ProcessedJuMPOptimiserAttributes\n | pr | HighOrderPrior\n | | pr | LowOrderPrior\n | | | X | 252×20 Matrix{Float64}\n | | | mu | 20-element Vector{Float64}\n | | | sigma | 20×20 Matrix{Float64}\n | | | chol | nothing\n | | | w | nothing\n | | | ens | nothing\n | | | kld | nothing\n | | | ow | nothing\n | | | rr | nothing\n | | | f_mu | nothing\n | | | f_sigma | nothing\n | | | f_w | nothing\n | | kt | 400×400 Matrix{Float64}\n | | L2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | S2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | sk | 20×400 Matrix{Float64}\n | | V | 20×20 Matrix{Float64}\n | | skmp | NonPositiveDefiniteMatrixProcessing\n | | | denoise | nothing\n | | | detone | nothing\n | | | alg | nothing\n | wb | WeightBounds\n | | lb | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | | ub | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | plg | nothing\n | tn | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n w_min_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n w_opt_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n w_max_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n noc_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}(:clarabel1 => Dict{Symbol, Any}(:settings => Dict{String, Bool}(\"verbose\" => 0), :err => solution_summary(; result = 1, verbose = false)\n | ├ solver_name : Clarabel\n | ├ Termination\n | │ ├ termination_status : SLOW_PROGRESS\n | │ ├ result_count : 1\n | │ └ raw_status : INSUFFICIENT_PROGRESS\n | ├ Solution (result = 1)\n | │ ├ primal_status : OTHER_RESULT_STATUS\n | │ ├ dual_status : OTHER_RESULT_STATUS\n | │ ├ objective_value : 9.29402e+01\n | │ └ dual_objective_value : 9.29052e+01\n | └ Work counters\n | ├ solve_time (sec) : 3.28402e-01\n | └ barrier_iterations : 35))\n retcode | OptimisationSuccess\n | res | nothing\n sol | PortfolioOptimisers.JuMPOptimisationSolution\n | w | 20-element Vector{Float64}\n model | A JuMP Model\n | ├ solver: Clarabel\n | ├ objective_sense: MIN_SENSE\n | │ └ objective_function_type: JuMP.AffExpr\n | ├ num_variables: 273\n | ├ num_constraints: 47\n | │ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonnegatives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonpositives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.SecondOrderCone: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.ExponentialCone: 42\n | │ └ Vector{JuMP.AffExpr} in MOI.PositiveSemidefiniteConeSquare: 1\n | └ Names registered in the model\n | └ :M, :M_PSD, :W, :bgt, :ckurt_soc_1, :clog_delta_w, :clog_ret, :clog_risk, :clog_w, :k, :log_delta_w, :log_ret, :log_risk, :log_w, :lw, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :sqrt_kurtosis_risk_1, :w, :w_lb, :w_ub, :zkurt_1\n" }, "metadata": {}, "execution_count": 8 @@ -378,7 +378,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "NearOptimalCentering\n opt | JuMPOptimiser\n | pe | HighOrderPrior\n | | pr | LowOrderPrior\n | | | X | 252×20 Matrix{Float64}\n | | | mu | 20-element Vector{Float64}\n | | | sigma | 20×20 Matrix{Float64}\n | | | chol | nothing\n | | | w | nothing\n | | | ens | nothing\n | | | kld | nothing\n | | | ow | nothing\n | | | rr | nothing\n | | | f_mu | nothing\n | | | f_sigma | nothing\n | | | f_w | nothing\n | | kt | 400×400 Matrix{Float64}\n | | L2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | S2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | sk | 20×400 Matrix{Float64}\n | | V | 20×20 Matrix{Float64}\n | | skmp | NonPositiveDefiniteMatrixProcessing\n | | | denoise | nothing\n | | | detone | nothing\n | | | alg | nothing\n | slv | 7-element Vector{Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3}\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | Vector{PortfolioOptimisers.RiskMeasure}: PortfolioOptimisers.RiskMeasure[NegativeSkewness\n settings | RiskMeasureSettings\n | scale | Float64: 1.0\n | ub | StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: 0.001440787172801399:0.0001018422377975618:0.0018481561239916463\n | rke | Bool: true\n mp | NonPositiveDefiniteMatrixProcessing\n | denoise | nothing\n | detone | nothing\n | alg | nothing\n sk | 20×400 Matrix{Float64}\n V | 20×20 Matrix{Float64}\n alg | SqrtRiskExpr()\n, SquareRootKurtosis\n settings | RiskMeasureSettings\n | scale | Float64: 1.0\n | ub | StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: 0.00023763665857707818:7.758491571902882e-6:0.0002686706248646897\n | rke | Bool: true\n w | nothing\n mu | 20-element Vector{Float64}\n kt | 400×400 Matrix{Float64}\n N | nothing\n alg | Full()\n]\n obj | MaximumReturn()\n bins | nothing\n w_min | nothing\n w_min_ini | nothing\n w_opt | nothing\n w_opt_ini | nothing\n w_max | nothing\n w_max_ini | nothing\n ucs_flag | Bool: true\n alg | UnconstrainedNearOptimalCentering()\n fallback | nothing\n" + "text/plain": "NearOptimalCentering\n opt | JuMPOptimiser\n | pe | HighOrderPrior\n | | pr | LowOrderPrior\n | | | X | 252×20 Matrix{Float64}\n | | | mu | 20-element Vector{Float64}\n | | | sigma | 20×20 Matrix{Float64}\n | | | chol | nothing\n | | | w | nothing\n | | | ens | nothing\n | | | kld | nothing\n | | | ow | nothing\n | | | rr | nothing\n | | | f_mu | nothing\n | | | f_sigma | nothing\n | | | f_w | nothing\n | | kt | 400×400 Matrix{Float64}\n | | L2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | S2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | sk | 20×400 Matrix{Float64}\n | | V | 20×20 Matrix{Float64}\n | | skmp | NonPositiveDefiniteMatrixProcessing\n | | | denoise | nothing\n | | | detone | nothing\n | | | alg | nothing\n | slv | 7-element Vector{Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3}\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | plg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | Vector{PortfolioOptimisers.RiskMeasure}: PortfolioOptimisers.RiskMeasure[NegativeSkewness\n settings | RiskMeasureSettings\n | scale | Float64: 1.0\n | ub | StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: 0.001440787172801399:0.0001018422377975618:0.0018481561239916463\n | rke | Bool: true\n mp | NonPositiveDefiniteMatrixProcessing\n | denoise | nothing\n | detone | nothing\n | alg | nothing\n sk | 20×400 Matrix{Float64}\n V | 20×20 Matrix{Float64}\n alg | SqrtRiskExpr()\n, SquareRootKurtosis\n settings | RiskMeasureSettings\n | scale | Float64: 1.0\n | ub | StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: 0.00023763665857707818:7.758491571902882e-6:0.0002686706248646897\n | rke | Bool: true\n w | nothing\n mu | 20-element Vector{Float64}\n kt | 400×400 Matrix{Float64}\n N | nothing\n alg | Full()\n]\n obj | MaximumReturn()\n bins | nothing\n w_min | nothing\n w_min_ini | nothing\n w_opt | nothing\n w_opt_ini | nothing\n w_max | nothing\n w_max_ini | nothing\n ucs_flag | Bool: true\n alg | UnconstrainedNearOptimalCentering()\n fallback | nothing\n" }, "metadata": {}, "execution_count": 12 @@ -403,7 +403,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "NearOptimalCenteringOptimisation\n oe | DataType: NearOptimalCentering\n pa | ProcessedJuMPOptimiserAttributes\n | pr | HighOrderPrior\n | | pr | LowOrderPrior\n | | | X | 252×20 Matrix{Float64}\n | | | mu | 20-element Vector{Float64}\n | | | sigma | 20×20 Matrix{Float64}\n | | | chol | nothing\n | | | w | nothing\n | | | ens | nothing\n | | | kld | nothing\n | | | ow | nothing\n | | | rr | nothing\n | | | f_mu | nothing\n | | | f_sigma | nothing\n | | | f_w | nothing\n | | kt | 400×400 Matrix{Float64}\n | | L2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | S2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | sk | 20×400 Matrix{Float64}\n | | V | 20×20 Matrix{Float64}\n | | skmp | NonPositiveDefiniteMatrixProcessing\n | | | denoise | nothing\n | | | detone | nothing\n | | | alg | nothing\n | wb | WeightBounds\n | | lb | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | | ub | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n w_min_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n w_opt_retcode | 25-element Vector{PortfolioOptimisers.OptimisationReturnCode}\n w_max_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n noc_retcode | 25-element Vector{PortfolioOptimisers.OptimisationReturnCode}\n retcode | OptimisationSuccess\n | res | nothing\n sol | 25-element Vector{PortfolioOptimisers.JuMPOptimisationSolution}\n model | A JuMP Model\n | ├ solver: Clarabel\n | ├ objective_sense: MIN_SENSE\n | │ └ objective_function_type: JuMP.AffExpr\n | ├ num_variables: 274\n | ├ num_constraints: 48\n | │ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonnegatives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonpositives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.SecondOrderCone: 2\n | │ ├ Vector{JuMP.AffExpr} in MOI.ExponentialCone: 42\n | │ └ Vector{JuMP.AffExpr} in MOI.PositiveSemidefiniteConeSquare: 1\n | └ Names registered in the model\n | └ :M_PSD, :W, :W_M, :bgt, :ckurt_soc_2, :clog_delta_w, :clog_ret, :clog_risk, :clog_w, :cnskew_soc_1, :k, :log_delta_w, :log_ret, :log_risk, :log_w, :lw, :nskew_risk_1, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :sqrt_kurtosis_risk_2, :w, :w_lb, :w_ub, :zkurt_2\n" + "text/plain": "NearOptimalCenteringOptimisation\n oe | DataType: NearOptimalCentering\n pa | ProcessedJuMPOptimiserAttributes\n | pr | HighOrderPrior\n | | pr | LowOrderPrior\n | | | X | 252×20 Matrix{Float64}\n | | | mu | 20-element Vector{Float64}\n | | | sigma | 20×20 Matrix{Float64}\n | | | chol | nothing\n | | | w | nothing\n | | | ens | nothing\n | | | kld | nothing\n | | | ow | nothing\n | | | rr | nothing\n | | | f_mu | nothing\n | | | f_sigma | nothing\n | | | f_w | nothing\n | | kt | 400×400 Matrix{Float64}\n | | L2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | S2 | 210×400 SparseArrays.SparseMatrixCSC{Int64, Int64}\n | | sk | 20×400 Matrix{Float64}\n | | V | 20×20 Matrix{Float64}\n | | skmp | NonPositiveDefiniteMatrixProcessing\n | | | denoise | nothing\n | | | detone | nothing\n | | | alg | nothing\n | wb | WeightBounds\n | | lb | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | | ub | 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | plg | nothing\n | tn | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n w_min_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n w_opt_retcode | 25-element Vector{PortfolioOptimisers.OptimisationReturnCode}\n w_max_retcode | OptimisationSuccess\n | res | Dict{Any, Any}: Dict{Any, Any}()\n noc_retcode | 25-element Vector{PortfolioOptimisers.OptimisationReturnCode}\n retcode | OptimisationSuccess\n | res | nothing\n sol | 25-element Vector{PortfolioOptimisers.JuMPOptimisationSolution}\n model | A JuMP Model\n | ├ solver: Clarabel\n | ├ objective_sense: MIN_SENSE\n | │ └ objective_function_type: JuMP.AffExpr\n | ├ num_variables: 274\n | ├ num_constraints: 48\n | │ ├ JuMP.AffExpr in MOI.EqualTo{Float64}: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonnegatives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.Nonpositives: 1\n | │ ├ Vector{JuMP.AffExpr} in MOI.SecondOrderCone: 2\n | │ ├ Vector{JuMP.AffExpr} in MOI.ExponentialCone: 42\n | │ └ Vector{JuMP.AffExpr} in MOI.PositiveSemidefiniteConeSquare: 1\n | └ Names registered in the model\n | └ :M, :M_PSD, :W, :bgt, :ckurt_soc_2, :clog_delta_w, :clog_ret, :clog_risk, :clog_w, :cnskew_soc_1, :k, :log_delta_w, :log_ret, :log_risk, :log_w, :lw, :nskew_risk_1, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :sqrt_kurtosis_risk_2, :w, :w_lb, :w_ub, :zkurt_2\n" }, "metadata": {}, "execution_count": 13 @@ -466,167 +466,167 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" ] }, "metadata": {}, @@ -662,82 +662,82 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" ] }, "metadata": {}, @@ -842,73 +842,73 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" ] }, "metadata": {}, diff --git a/examples/5_Budget_Constraints.ipynb b/examples/5_Budget_Constraints.ipynb index dca5b100b..8834b6373 100644 --- a/examples/5_Budget_Constraints.ipynb +++ b/examples/5_Budget_Constraints.ipynb @@ -175,7 +175,7 @@ { "output_type": "execute_result", "data": { - "text/plain": "MeanRisk\n opt | JuMPOptimiser\n | pe | LowOrderPrior\n | | X | 252×20 Matrix{Float64}\n | | mu | 20-element Vector{Float64}\n | | sigma | 20×20 Matrix{Float64}\n | | chol | nothing\n | | w | nothing\n | | ens | nothing\n | | kld | nothing\n | | ow | nothing\n | | rr | nothing\n | | f_mu | nothing\n | | f_sigma | nothing\n | | f_w | nothing\n | slv | Vector{Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3}: Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3[Solver\n | name | Symbol: :clarabel1\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n | , Solver\n | name | Symbol: :clarabel3\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Real}: Dict{String, Real}(\"verbose\" => false, \"max_step_fraction\" => 0.9)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n | , Solver\n | name | Symbol: :clarabel5\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Real}: Dict{String, Real}(\"verbose\" => false, \"max_step_fraction\" => 0.8)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n | , Solver\n | name | Symbol: :clarabel7\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Real}: Dict{String, Real}(\"verbose\" => false, \"max_step_fraction\" => 0.7)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n | ]\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | nplg | nothing\n | cplg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | EntropicValueatRisk\n | settings | RiskMeasureSettings\n | | scale | Float64: 1.0\n | | ub | nothing\n | | rke | Bool: true\n | slv | nothing\n | alpha | Float64: 0.05\n | w | nothing\n obj | MinimumRisk()\n wi | nothing\n fallback | nothing\n" + "text/plain": "MeanRisk\n opt | JuMPOptimiser\n | pe | LowOrderPrior\n | | X | 252×20 Matrix{Float64}\n | | mu | 20-element Vector{Float64}\n | | sigma | 20×20 Matrix{Float64}\n | | chol | nothing\n | | w | nothing\n | | ens | nothing\n | | kld | nothing\n | | ow | nothing\n | | rr | nothing\n | | f_mu | nothing\n | | f_sigma | nothing\n | | f_w | nothing\n | slv | Vector{Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3}: Solver{Symbol, UnionAll, T3, @NamedTuple{allow_local::Bool, allow_almost::Bool}, Bool} where T3[Solver\n | name | Symbol: :clarabel1\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Bool}: Dict{String, Bool}(\"verbose\" => 0)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n | , Solver\n | name | Symbol: :clarabel3\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Real}: Dict{String, Real}(\"verbose\" => false, \"max_step_fraction\" => 0.9)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n | , Solver\n | name | Symbol: :clarabel5\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Real}: Dict{String, Real}(\"verbose\" => false, \"max_step_fraction\" => 0.8)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n | , Solver\n | name | Symbol: :clarabel7\n | solver | UnionAll: Clarabel.MOIwrapper.Optimizer\n | settings | Dict{String, Real}: Dict{String, Real}(\"verbose\" => false, \"max_step_fraction\" => 0.7)\n | check_sol | @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)\n | add_bridges | Bool: true\n | ]\n | wb | WeightBounds\n | | lb | Float64: 0.0\n | | ub | Float64: 1.0\n | bgt | Float64: 1.0\n | sbgt | nothing\n | lt | nothing\n | st | nothing\n | lcs | nothing\n | lcm | nothing\n | cent | nothing\n | gcard | nothing\n | sgcard | nothing\n | smtx | nothing\n | sgmtx | nothing\n | slt | nothing\n | sst | nothing\n | sglt | nothing\n | sgst | nothing\n | sets | nothing\n | plg | nothing\n | tn | nothing\n | te | nothing\n | fees | nothing\n | ret | ArithmeticReturn\n | | ucs | nothing\n | | lb | nothing\n | sce | SumScalariser: SumScalariser()\n | ccnt | nothing\n | cobj | nothing\n | sc | Int64: 1\n | so | Int64: 1\n | card | nothing\n | scard | nothing\n | nea | nothing\n | l1 | nothing\n | l2 | nothing\n | ss | nothing\n | strict | Bool: false\n r | EntropicValueatRisk\n | settings | RiskMeasureSettings\n | | scale | Float64: 1.0\n | | ub | nothing\n | | rke | Bool: true\n | slv | nothing\n | alpha | Float64: 0.05\n | w | nothing\n obj | MinimumRisk()\n wi | nothing\n fallback | nothing\n" }, "metadata": {}, "execution_count": 5 diff --git a/src/11_Phylogeny/5_Phylogeny.jl b/src/11_Phylogeny/5_Phylogeny.jl index 9ee67ac49..46e6c698f 100644 --- a/src/11_Phylogeny/5_Phylogeny.jl +++ b/src/11_Phylogeny/5_Phylogeny.jl @@ -28,7 +28,7 @@ Keyword arguments correspond to the fields above. - If `X` is a matrix: + Must be symmetric, `issymmetric(X) == true`. - + Must have zero diagonal, `all(x -> iszero(x), diag(X)) == true`. + + Must have zero diagonal, `all(iszero, diag(X)) == true`. # Examples @@ -54,7 +54,7 @@ struct PhylogenyResult{T} <: AbstractPhylogenyResult @argcheck(!isempty(X)) if isa(X, AbstractMatrix) @argcheck(issymmetric(X)) - @argcheck(all(x -> iszero(x), diag(X))) + @argcheck(all(iszero, diag(X))) end return new{typeof(X)}(X) end diff --git a/src/12_ConstraintGeneration/2_LinearConstraintGeneration.jl b/src/12_ConstraintGeneration/2_LinearConstraintGeneration.jl index d13463b70..1cbb59c8b 100644 --- a/src/12_ConstraintGeneration/2_LinearConstraintGeneration.jl +++ b/src/12_ConstraintGeneration/2_LinearConstraintGeneration.jl @@ -1180,8 +1180,8 @@ function get_linear_constraints(lcs::Union{<:ParsingResult, end At += Ai * c end - @argcheck(any(x -> !iszero(x), At), - DomainError("At least one entry in At must be non-zero:\nany(x -> !iszero(x), At) => $(any(x -> !iszero(x), At))")) + @argcheck(any(!iszero, At), + DomainError("At least one entry in At must be non-zero:\nany(!iszero, At) => $(any(!iszero, At))")) d = ifelse(lc.op == ">=", -1, 1) flag = d == -1 || lc.op == "<=" A = At .* d diff --git a/src/12_ConstraintGeneration/3_PhylogenyConstraintGeneration.jl b/src/12_ConstraintGeneration/3_PhylogenyConstraintGeneration.jl index 24ddc777f..bbfe07d2b 100644 --- a/src/12_ConstraintGeneration/3_PhylogenyConstraintGeneration.jl +++ b/src/12_ConstraintGeneration/3_PhylogenyConstraintGeneration.jl @@ -140,7 +140,7 @@ SemiDefinitePhylogeny(; ## Validation - - `issymmetric(A)` and `all(x -> iszero(x), diag(A))`. + - `issymmetric(A)` and `all(iszero, diag(A))`. - `p >= 0`. # Examples @@ -162,7 +162,7 @@ struct SemiDefinitePhylogeny{T1, T2} <: AbstractPhylogenyConstraintResult A::T1 p::T2 function SemiDefinitePhylogeny(A::AbstractMatrix{<:Real}, p::Real) - @argcheck(all(x -> iszero(x), diag(A))) + @argcheck(all(iszero, diag(A))) @argcheck(issymmetric(A)) @argcheck(p >= zero(p)) return new{typeof(A), typeof(p)}(A, p) @@ -323,7 +323,7 @@ IntegerPhylogeny(; ## Validation - - `issymmetric(A)` and `all(x -> iszero(x), diag(A))`. + - `issymmetric(A)` and `all(iszero, diag(A))`. - If `B` is a vector: `!isempty(B)`, `all(x -> x >= 0, B)`, and `size(unique(A + I; dims = 1), 1) == length(B)`. - If `B` is an integer: `B >= 0`. @@ -349,7 +349,7 @@ struct IntegerPhylogeny{T1, T2, T3} <: AbstractPhylogenyConstraintResult scale::T3 function IntegerPhylogeny(A::AbstractMatrix{<:Real}, B::Union{<:Integer, <:AbstractVector{<:Integer}}, scale::Real) - @argcheck(all(x -> iszero(x), diag(A))) + @argcheck(all(iszero, diag(A))) @argcheck(issymmetric(A)) A = unique(A + I; dims = 1) if isa(B, AbstractVector) @@ -428,6 +428,11 @@ function phylogeny_constraints(plc::Union{<:SemiDefinitePhylogeny, <:IntegerPhyl Nothing}, args...; kwargs...) return plc end +function phylogeny_constraints(plcs::AbstractVector{<:Union{<:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult}}, + args...; kwargs...) + return [phylogeny_constraints(plc, args...; kwargs...) for plc in plcs] +end """ ```julia abstract type VectorToRealMeasure <: AbstractAlgorithm end diff --git a/src/13_Prior/1_Base_Prior.jl b/src/13_Prior/1_Base_Prior.jl index aef191022..f69018806 100644 --- a/src/13_Prior/1_Base_Prior.jl +++ b/src/13_Prior/1_Base_Prior.jl @@ -1,53 +1,353 @@ +""" +```julia +abstract type AbstractPriorEstimator <: AbstractEstimator end +``` + +Abstract supertype for all prior estimators. + +`AbstractPriorEstimator` is the base type for all estimators that compute prior information from asset and/or factor returns. All concrete prior estimators should subtype this type to ensure a consistent interface for prior computation and integration with portfolio optimisation workflows. + +# Related + + - [`AbstractLowOrderPriorEstimator`](@ref) + - [`AbstractHighOrderPriorEstimator`](@ref) + - [`prior`](@ref) +""" abstract type AbstractPriorEstimator <: AbstractEstimator end +""" +```julia abstract type AbstractLowOrderPriorEstimator <: AbstractPriorEstimator end +``` + +Abstract supertype for low order prior estimators. + +`AbstractLowOrderPriorEstimator` is the base type for estimators that compute low order moments (mean and covariance) from asset and/or factor returns. All concrete low order prior estimators should subtype this type for consistent moment estimation and integration. + +# Related + + - [`AbstractPriorEstimator`](@ref) + - [`AbstractLowOrderPriorEstimator_A`](@ref) + - [`AbstractLowOrderPriorEstimator_F`](@ref) + - [`AbstractLowOrderPriorEstimator_AF`](@ref) """ -Only uses asset returns +abstract type AbstractLowOrderPriorEstimator <: AbstractPriorEstimator end """ +```julia abstract type AbstractLowOrderPriorEstimator_A <: AbstractLowOrderPriorEstimator end +``` + +Low order prior estimator using only asset returns. + +`AbstractLowOrderPriorEstimator_A` is the base type for estimators that compute low order moments (mean and covariance) using only asset returns data. All concrete asset-only prior estimators should subtype this type. + +# Related + + - [`AbstractLowOrderPriorEstimator`](@ref) + - [`AbstractLowOrderPriorEstimator_F`](@ref) + - [`AbstractLowOrderPriorEstimator_AF`](@ref) """ -Uses asset and factor returns. +abstract type AbstractLowOrderPriorEstimator_A <: AbstractLowOrderPriorEstimator end +""" +```julia +abstract type AbstractLowOrderPriorEstimator_F <: AbstractLowOrderPriorEstimator end +``` + +Low order prior estimator using factor returns. + +`AbstractLowOrderPriorEstimator_F` is the base type for estimators that compute low order moments (mean and covariance) requiring the use of both asset and factor returns data. All concrete factor-adjusted prior estimators should subtype this type. + +# Related + + - [`AbstractLowOrderPriorEstimator`](@ref) + - [`AbstractLowOrderPriorEstimator_A`](@ref) + - [`AbstractLowOrderPriorEstimator_AF`](@ref) """ abstract type AbstractLowOrderPriorEstimator_F <: AbstractLowOrderPriorEstimator end """ -Uses asset returns and optionally factor returns. +```julia +abstract type AbstractLowOrderPriorEstimator_AF <: AbstractLowOrderPriorEstimator end +``` + +Low order prior estimator using both asset and factor returns. + +`AbstractLowOrderPriorEstimator_AF` is the base type for estimators that compute low order moments (mean and covariance) using both asset and optionally factor returns data. All concrete prior estimators which may optionally use factor returns should subtype this type. + +# Related + + - [`AbstractLowOrderPriorEstimator`](@ref) + - [`AbstractLowOrderPriorEstimator_A`](@ref) + - [`AbstractLowOrderPriorEstimator_F`](@ref) """ abstract type AbstractLowOrderPriorEstimator_AF <: AbstractLowOrderPriorEstimator end +""" +```julia +const AbstractLowOrderPriorEstimator_A_AF = Union{AbstractLowOrderPriorEstimator_A, + AbstractLowOrderPriorEstimator_AF} +``` + +Union type for asset-only and asset-and-factor low order prior estimators. + +`AbstractLowOrderPriorEstimator_A_AF` is a union type that allows dispatch on both asset-only and asset-and-factor prior estimators. This is useful for generic algorithms that operate on estimators using asset returns, with or without factor returns. + +# Related + - [`AbstractLowOrderPriorEstimator_A`](@ref) + - [`AbstractLowOrderPriorEstimator_AF`](@ref) + - [`AbstractLowOrderPriorEstimator_F_AF`](@ref) + - [`AbstractLowOrderPriorEstimator_A_F_AF`](@ref) +""" const AbstractLowOrderPriorEstimator_A_AF = Union{AbstractLowOrderPriorEstimator_A, AbstractLowOrderPriorEstimator_AF} +""" +```julia const AbstractLowOrderPriorEstimator_F_AF = Union{AbstractLowOrderPriorEstimator_F, AbstractLowOrderPriorEstimator_AF} +``` + +Union type for factor-only and asset-and-factor low order prior estimators. + +`AbstractLowOrderPriorEstimator_F_AF` is a union type that allows dispatch on both factor-only and asset-and-factor prior estimators. This is useful for generic algorithms that operate on estimators using factor returns, with or without asset returns. + +# Related + + - [`AbstractLowOrderPriorEstimator_F`](@ref) + - [`AbstractLowOrderPriorEstimator_AF`](@ref) + - [`AbstractLowOrderPriorEstimator_A_AF`](@ref) + - [`AbstractLowOrderPriorEstimator_A_F_AF`](@ref) +""" +const AbstractLowOrderPriorEstimator_F_AF = Union{AbstractLowOrderPriorEstimator_F, + AbstractLowOrderPriorEstimator_AF} +""" +```julia const AbstractLowOrderPriorEstimator_A_F_AF = Union{AbstractLowOrderPriorEstimator_A, AbstractLowOrderPriorEstimator_F, AbstractLowOrderPriorEstimator_AF} +``` + +Union type for asset-only, factor-only, and asset-and-factor low order prior estimators. +`AbstractLowOrderPriorEstimator_A_F_AF` is a union type that allows dispatch on asset-only, factor-only, and asset-and-factor prior estimators. This is useful for generic algorithms that operate on estimators using any combination of asset and factor returns. + +# Related + + - [`AbstractLowOrderPriorEstimator_A`](@ref) + - [`AbstractLowOrderPriorEstimator_F`](@ref) + - [`AbstractLowOrderPriorEstimator_AF`](@ref) + - [`AbstractLowOrderPriorEstimator_A_AF`](@ref) + - [`AbstractLowOrderPriorEstimator_F_AF`](@ref) """ +const AbstractLowOrderPriorEstimator_A_F_AF = Union{AbstractLowOrderPriorEstimator_A, + AbstractLowOrderPriorEstimator_F, + AbstractLowOrderPriorEstimator_AF} """ +```julia abstract type AbstractHighOrderPriorEstimator <: AbstractPriorEstimator end +``` + +Abstract supertype for high order prior estimators. + +`AbstractHighOrderPriorEstimator` is the base type for estimators that compute high order moments (such as coskewness and cokurtosis) from asset and/or factor returns. All concrete high order prior estimators should subtype this type to ensure a consistent interface for higher moment estimation and integration with portfolio optimisation workflows. + +# Related + + - [`AbstractPriorEstimator`](@ref) + - [`AbstractLowOrderPriorEstimator`](@ref) + - [`prior`](@ref) """ +abstract type AbstractHighOrderPriorEstimator <: AbstractPriorEstimator end """ +```julia abstract type AbstractPriorResult <: AbstractResult end +``` + +Abstract supertype for all prior result types. + +`AbstractPriorResult` is the base type for all result objects produced by prior estimators, containing computed prior information such as moments, asset returns, and factor returns. All concrete prior result types should subtype this to ensure a consistent interface for integration with portfolio optimisation workflows. + +# Related + + - [`AbstractPriorEstimator`](@ref) + - [`prior`](@ref) + - [`AbstractResult`](@ref) +""" +abstract type AbstractPriorResult <: AbstractResult end +""" +```julia +prior(pr::AbstractPriorEstimator, rd::ReturnsResult; kwargs...) +``` + +Compute prior information from asset and/or factor returns using a prior estimator. + +`prior` applies the specified prior estimator to a `ReturnsResult` object, extracting asset and factor returns and passing them, along with any additional information, to the estimator. Returns a prior result containing computed moments and other prior information for use in portfolio optimisation workflows. + +# Arguments + + - `pr`: Prior estimator. + - `rd`: Asset and/or factor returns result. + - `kwargs...`: Additional keyword arguments passed to the estimator. + +# Returns + + - `pr::AbstractPriorResult`: Result object containing computed prior information. + +# Related + + - [`AbstractPriorEstimator`](@ref) + - [`ReturnsResult`](@ref) + - [`AbstractPriorResult`](@ref) +""" function prior(pr::AbstractPriorEstimator, rd::ReturnsResult; kwargs...) return prior(pr, rd.X, rd.F; iv = rd.iv, ivpa = rd.ivpa, kwargs...) end -function prior_view(pr::AbstractPriorEstimator, args...; kwargs...) +""" +```julia +prior(pr::AbstractPriorResult, args...; kwargs...) +``` + +Propagate or pass through prior result objects. + +`prior` returns the input prior result object unchanged. This method is used to propagate already constructed prior results or enable uniform interface handling in workflows that accept either estimators or results. + +# Arguments + + - `pr`: Prior result object. + - `args...`: Additional positional arguments (ignored). + - `kwargs...`: Additional keyword arguments (ignored). + +# Returns + + - `res::AbstractPriorResult`: The input prior result object, unchanged. + +# Related + + - [`AbstractPriorResult`](@ref) + - [`prior`](@ref) +""" +function prior(pr::AbstractPriorResult, args...; kwargs...) return pr end -function prior(pr::AbstractPriorResult, args...; kwargs...) +function prior_view(pr::AbstractPriorEstimator, args...; kwargs...) return pr end +""" +```julia +clusterise(cle::ClusteringEstimator, pr::AbstractPriorResult; kwargs...) +``` + +Clusterise asset or factor returns from a prior result using a clustering estimator. + +`clusterise` applies the specified clustering estimator to the asset returns matrix contained in the prior result object, producing a clustering result for use in phylogeny analysis, constraint generation, or portfolio construction. + +# Arguments + + - `cle`: Clustering estimator. + - `pr`: Prior result object. + - `kwargs...`: Additional keyword arguments passed to the clustering estimator. + +# Returns + + - `res::AbstractClusteringResult`: Result object containing clustering information. + +# Related + + - [`ClusteringEstimator`](@ref) + - [`AbstractPriorResult`](@ref) + - [`clusterise`](@ref) +""" function clusterise(cle::ClusteringEstimator, pr::AbstractPriorResult; kwargs...) return clusterise(cle, pr.X; kwargs...) end +""" +```julia +phylogeny_matrix(necle::Union{<:AbstractNetworkEstimator, <:AbstractClusteringEstimator, + <:AbstractClusteringResult}, pr::AbstractPriorResult; + kwargs...) +``` + +Compute the phylogeny matrix from asset returns in a prior result using a network or clustering estimator. + +`phylogeny_matrix` applies the specified network or clustering estimator to the asset returns matrix contained in the prior result object, producing a phylogeny matrix for use in constraint generation, centrality analysis, or portfolio construction. + +# Arguments + + - `necle`: Network estimator, clustering estimator, or clustering result. + - `pr`: Prior result object. + - `kwargs...`: Additional keyword arguments passed to the estimator. + +# Returns + + - `res::PhylogenyResult`: Result object containing the phylogeny matrix. + +# Related + + - [`NetworkEstimator`](@ref) + - [`ClusteringEstimator`](@ref) + - [`PhylogenyResult`](@ref) + - [`phylogeny_matrix`](@ref) +""" function phylogeny_matrix(necle::Union{<:AbstractNetworkEstimator, <:AbstractClusteringEstimator, <:AbstractClusteringResult}, pr::AbstractPriorResult; kwargs...) return phylogeny_matrix(necle, pr.X; kwargs...) end +""" +```julia +centrality_vector(necte::CentralityEstimator, pr::AbstractPriorResult; kwargs...) +``` + +Compute the centrality vector for a centrality estimator and prior result. + +`centrality_vector` applies the centrality algorithm in the estimator to the network constructed from the asset returns in the prior result, returning centrality scores for each asset. + +# Arguments + + - `necte`: Centrality estimator. + - `pr`: Prior result object. + - `kwargs...`: Additional keyword arguments. + +# Returns + + - `res::PhylogenyResult`: Result object containing the centrality vector. + +# Related + + - [`CentralityEstimator`](@ref) + - [`PhylogenyResult`](@ref) + - [`centrality_vector`](@ref) +""" function centrality_vector(necte::CentralityEstimator, pr::AbstractPriorResult; kwargs...) return centrality_vector(necte, pr.X; kwargs...) end +""" +```julia +centrality_vector(ne::Union{<:AbstractNetworkEstimator, <:AbstractClusteringEstimator, + <:AbstractClusteringResult}, cent::AbstractCentralityAlgorithm, + pr::AbstractPriorResult; kwargs...) +``` + +Compute the centrality vector for a network or clustering estimator and centrality algorithm. + +`centrality_vector` constructs the phylogeny matrix from the asset returns in the prior result, builds a graph, and computes node centrality scores using the specified centrality algorithm. + +# Arguments + + - `ne`: Network estimator, clustering estimator, or clustering result. + - `cent`: Centrality algorithm. + - `pr`: Prior result object. + - `kwargs...`: Additional keyword arguments. + +# Returns + + - `res::PhylogenyResult`: Result object containing the centrality vector. + +# Related + + - [`NetworkEstimator`](@ref) + - [`CentralityEstimator`](@ref) + - [`PhylogenyResult`](@ref) + - [`centrality_vector`](@ref) +""" function centrality_vector(ne::Union{<:AbstractNetworkEstimator, <:AbstractClusteringEstimator, <:AbstractClusteringResult}, @@ -55,15 +355,355 @@ function centrality_vector(ne::Union{<:AbstractNetworkEstimator, kwargs...) return centrality_vector(ne, cent, pr.X; kwargs...) end +""" +```julia +average_centrality(ne::Union{<:AbstractPhylogenyEstimator, <:AbstractPhylogenyResult}, + cent::AbstractCentralityAlgorithm, w::AbstractVector, + pr::AbstractPriorResult; kwargs...) +``` + +Compute the weighted average centrality for a network or phylogeny result. + +`average_centrality` computes the centrality vector using the specified network or phylogeny estimator and centrality algorithm, then returns the weighted average using the provided portfolio weights. + +# Arguments + + - `ne`: Network estimator or phylogeny result. + - `cent`: Centrality algorithm. + - `w`: Portfolio weights vector. + - `pr`: Prior result object. + - `kwargs...`: Additional keyword arguments. + +# Returns + + - `ac::Real`: Weighted average centrality. + +# Related + + - [`NetworkEstimator`](@ref) + - [`CentralityEstimator`](@ref) + - [`centrality_vector`](@ref) + - [`average_centrality`](@ref) +""" function average_centrality(ne::Union{<:AbstractPhylogenyEstimator, <:AbstractPhylogenyResult}, cent::AbstractCentralityAlgorithm, w::AbstractVector, pr::AbstractPriorResult; kwargs...) return dot(centrality_vector(ne, cent, pr.X; kwargs...).X, w) end +""" +```julia +average_centrality(cte::CentralityEstimator, w::AbstractVector, pr::AbstractPriorResult; + kwargs...) +``` + +Compute the weighted average centrality for a centrality estimator. + +`average_centrality` applies the centrality algorithm in the estimator to the network constructed from the asset returns in the prior result, then returns the weighted average using the provided portfolio weights. + +# Arguments + + - `cte`: Centrality estimator. + - `w`: Portfolio weights vector. + - `pr`: Prior result object. + - `kwargs...`: Additional keyword arguments. + +# Returns + + - `ac::Real`: Weighted average centrality. + +# Related + + - [`CentralityEstimator`](@ref) + - [`centrality_vector`](@ref) + - [`average_centrality`](@ref) +""" function average_centrality(cte::CentralityEstimator, w::AbstractVector, pr::AbstractPriorResult; kwargs...) return average_centrality(cte.ne, cte.cent, w, pr.X; kwargs...) end +""" +```julia +struct LowOrderPrior{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12} <: + AbstractPriorResult + X::T1 + mu::T2 + sigma::T3 + chol::T4 + w::T5 + ens::T6 + kld::T7 + ow::T8 + rr::T9 + f_mu::T10 + f_sigma::T11 + f_w::T12 +end +``` + +Container type for low order prior results in PortfolioOptimisers.jl. + +`LowOrderPrior` stores the output of low order prior estimation routines, including asset returns, mean vector, covariance matrix, Cholesky factor, weights, entropy, Kullback-Leibler divergence, outlier weights, regression results, and optional factor moments. It is used throughout the package to represent validated prior information for portfolio optimisation and analytics. + +# Fields + + - `X`: Asset returns matrix. + - `mu`: Mean vector. + - `sigma`: Covariance matrix. + - `chol`: Cholesky factorisation of the factor-adjusted covariance matrix. Factor models sparsify the covariance matrix, so using their smaller, sparser Cholesky factor makes for more numerically stable and efficient optimisations. + - `w`: Asset weights. + - `ens`: Entropy. + - `kld`: Kullback-Leibler divergence. + - `ow`: Opinion pooling weights. + - `rr`: Regression result. + - `f_mu`: Factor mean vector. + - `f_sigma`: Factor covariance matrix. + - `f_w`: Factor weights. + +# Constructor + +```julia +LowOrderPrior(X::AbstractMatrix, mu::AbstractVector, sigma::AbstractMatrix; + chol::Union{Nothing, <:AbstractMatrix} = nothing, + w::Union{Nothing, <:AbstractWeights} = nothing, + ens::Union{Nothing, <:Real} = nothing, + kld::Union{Nothing, <:Real, <:AbstractVector{<:Real}} = nothing, + ow::Union{Nothing, <:AbstractVector} = nothing, + rr::Union{Nothing, <:Regression} = nothing, + f_mu::Union{Nothing, <:AbstractVector} = nothing, + f_sigma::Union{Nothing, <:AbstractMatrix} = nothing, + f_w::Union{Nothing, <:AbstractVector} = nothing) +``` + +Keyword arguments correspond to the fields above. + +## Validation + + - `X`, `mu`, and `sigma` must be non-empty. + - `size(X, 2) == length(mu)`. + - `sigma` must be square. + - If `w` is provided, it must be non-empty and `length(w) == size(X, 1)`. + - If `kld` is a vector, it must be non-empty. + - If `ow` is provided, it must be non-empty. + - If regression or factor moments are provided, all must be present and non-empty, and dimensions must match. + - If `chol` or `f_w` are provided, they must be non-empty and have correct dimensions. + +# Examples + +```jldoctest +julia> LowOrderPrior(; X = [0.01 0.02; 0.03 0.04], mu = [0.02, 0.03], + sigma = [0.0001 0.0002; 0.0002 0.0003]) +LowOrderPrior + X | 2×2 Matrix{Float64} + mu | Vector{Float64}: [0.02, 0.03] + sigma | 2×2 Matrix{Float64} + chol | nothing + w | nothing + ens | nothing + kld | nothing + ow | nothing + rr | nothing + f_mu | nothing + f_sigma | nothing + f_w | nothing +``` + +# Related + + - [`AbstractPriorResult`](@ref) + - [`prior`](@ref) + - [`HighOrderPrior`](@ref) +""" +struct LowOrderPrior{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12} <: + AbstractPriorResult + X::T1 + mu::T2 + sigma::T3 + chol::T4 + w::T5 + ens::T6 + kld::T7 + ow::T8 + rr::T9 + f_mu::T10 + f_sigma::T11 + f_w::T12 + function LowOrderPrior(X::AbstractMatrix, mu::AbstractVector, sigma::AbstractMatrix, + chol::Union{Nothing, <:AbstractMatrix}, + w::Union{Nothing, <:AbstractWeights}, + ens::Union{Nothing, <:Real}, + kld::Union{Nothing, <:Real, <:AbstractVector{<:Real}}, + ow::Union{Nothing, <:AbstractVector}, + rr::Union{Nothing, <:Regression}, + f_mu::Union{Nothing, <:AbstractVector}, + f_sigma::Union{Nothing, <:AbstractMatrix}, + f_w::Union{Nothing, <:AbstractVector}) + @argcheck(!isempty(X) && !isempty(mu) && !isempty(sigma)) + @argcheck(size(X, 2) == length(mu)) + assert_matrix_issquare(sigma) + if !isnothing(w) + @argcheck(!isempty(w)) + @argcheck(length(w) == size(X, 1)) + end + if isa(kld, AbstractVector) + @argcheck(!isempty(kld)) + end + if !isnothing(ow) + @argcheck(!isempty(ow)) + end + loadings_flag = !isnothing(rr) + f_mu_flag = !isnothing(f_mu) + f_sigma_flag = !isnothing(f_sigma) + if loadings_flag || f_mu_flag || f_sigma_flag + @argcheck(loadings_flag && f_mu_flag && f_sigma_flag) + @argcheck(!isempty(f_mu) && !isempty(f_sigma)) + assert_matrix_issquare(f_sigma) + @argcheck(size(rr.M, 2) == length(f_mu) == size(f_sigma, 1)) + @argcheck(size(rr.M, 1) == length(mu)) + if !isnothing(chol) + @argcheck(!isempty(chol)) + @argcheck(length(mu) == size(chol, 2)) + end + if !isnothing(f_w) + @argcheck(!isempty(f_w)) + @argcheck(length(f_w) == size(X, 1)) + end + end + return new{typeof(X), typeof(mu), typeof(sigma), typeof(chol), typeof(w), + typeof(ens), typeof(kld), typeof(ow), typeof(rr), typeof(f_mu), + typeof(f_sigma), typeof(f_w)}(X, mu, sigma, chol, w, ens, kld, ow, rr, + f_mu, f_sigma, f_w) + end +end +function LowOrderPrior(; X::AbstractMatrix, mu::AbstractVector, sigma::AbstractMatrix, + chol::Union{Nothing, <:AbstractMatrix} = nothing, + w::Union{Nothing, <:AbstractWeights} = nothing, + ens::Union{Nothing, <:Real} = nothing, + kld::Union{Nothing, <:Real, <:AbstractVector{<:Real}} = nothing, + ow::Union{Nothing, <:AbstractVector} = nothing, + rr::Union{Nothing, <:Regression} = nothing, + f_mu::Union{Nothing, <:AbstractVector} = nothing, + f_sigma::Union{Nothing, <:AbstractMatrix} = nothing, + f_w::Union{Nothing, <:AbstractVector} = nothing) + return LowOrderPrior(X, mu, sigma, chol, w, ens, kld, ow, rr, f_mu, f_sigma, f_w) +end +function prior_view(pr::LowOrderPrior, i::AbstractVector) + chol = isnothing(pr.chol) ? nothing : view(pr.chol, :, i) + return LowOrderPrior(; X = view(pr.X, :, i), mu = view(pr.mu, i), + sigma = view(pr.sigma, i, i), chol = chol, w = pr.w, ens = pr.ens, + kld = pr.kld, ow = pr.ow, rr = regression_view(pr.rr, i), + f_mu = pr.f_mu, f_sigma = pr.f_sigma, f_w = pr.f_w) +end +""" +```julia +struct HighOrderPrior{T1, T2, T3, T4, T5, T6, T7} <: AbstractPriorResult + pr::T1 + kt::T2 + L2::T3 + S2::T4 + sk::T5 + V::T6 + skmp::T7 +end +``` + +Container type for high order prior results in PortfolioOptimisers.jl. + +`HighOrderPrior` stores the output of high order prior estimation routines, including low order prior results, cokurtosis tensor, elimination and summation matrices, coskewness tensor, quadratic skewness matrix, and matrix processing estimator. It is used throughout the package to represent validated prior information for portfolio optimisation and analytics involving higher moments. + +# Fields + + - `pr`: Prior result for low order moments (`AbstractPriorResult`). + - `kt`: Cokurtosis tensor (`N^2 × N^2` matrix). + - `L2`: Elimination matrix (`div(N * (N + 1), 2) × N^2`). + - `S2`: Summation matrix (`div(N * (N + 1), 2) × N^2`). + - `sk`: Coskewness tensor (`N × N^2` matrix). + - `V`: Negative quadratic skewness matrix (`N × N`). + - `skmp`: Matrix processing estimator for post-processing quadratic skewness. + +# Constructor + +```julia +HighOrderPrior(pr::AbstractPriorResult, kt::Union{Nothing, <:AbstractMatrix}, + L2::Union{Nothing, <:AbstractMatrix}, S2::Union{Nothing, <:AbstractMatrix}, + sk::Union{Nothing, <:AbstractMatrix}, V::Union{Nothing, <:AbstractMatrix}, + skmp::Union{Nothing, <:AbstractMatrixProcessingEstimator}) +HighOrderPrior(; pr::AbstractPriorResult, kt::Union{Nothing, <:AbstractMatrix} = nothing, + L2::Union{Nothing, <:AbstractMatrix} = nothing, + S2::Union{Nothing, <:AbstractMatrix} = nothing, + sk::Union{Nothing, <:AbstractMatrix} = nothing, + V::Union{Nothing, <:AbstractMatrix} = nothing, + skmp::Union{Nothing, <:AbstractMatrixProcessingEstimator} = nothing) +``` + +Keyword arguments correspond to the fields above. + +## Validation + + - If any of `kt`, `L2`, or `S2` are provided, all must be provided, non-empty and have compatible dimensions. + - If `sk` or `V` are provided, both must be non-empty and have compatible dimensions. + - If only one of `sk` or `V` is provided, both must be provided. + +# Examples + +# Related + + - [`AbstractPriorResult`](@ref) + - [`LowOrderPrior`](@ref) + - [`HighOrderPriorEstimator`](@ref) + - [`prior`](@ref) +""" +struct HighOrderPrior{T1, T2, T3, T4, T5, T6, T7} <: AbstractPriorResult + pr::T1 + kt::T2 + L2::T3 + S2::T4 + sk::T5 + V::T6 + skmp::T7 + function HighOrderPrior(pr::AbstractPriorResult, kt::Union{Nothing, <:AbstractMatrix}, + L2::Union{Nothing, <:AbstractMatrix}, + S2::Union{Nothing, <:AbstractMatrix}, + sk::Union{Nothing, <:AbstractMatrix}, + V::Union{Nothing, <:AbstractMatrix}, + skmp::Union{Nothing, <:AbstractMatrixProcessingEstimator}) + kt_flag = isa(kt, AbstractMatrix) + L2_flag = isa(L2, AbstractMatrix) + S2_flag = isa(S2, AbstractMatrix) + if kt_flag || L2_flag || S2_flag + @argcheck(kt_flag && L2_flag && S2_flag) + @argcheck(!isempty(kt) && !isempty(L2) && !isempty(S2)) + assert_matrix_issquare(kt) + N = length(pr.mu) + @argcheck(length(pr.mu)^2 == size(kt, 1)) + @argcheck(size(L2) == size(S2) == (div(N * (N + 1), 2), N^2)) + end + sk_flag = isa(sk, AbstractMatrix) + V_flag = isa(V, AbstractMatrix) + if sk_flag + @argcheck(!isempty(sk)) + @argcheck(length(pr.mu)^2 == size(sk, 2)) + end + if V_flag + @argcheck(!isempty(V)) + assert_matrix_issquare(V) + end + if sk_flag || V_flag + @argcheck(sk_flag && V_flag, + "If either sk or V, is nothing, both must be nothing.") + end + return new{typeof(pr), typeof(kt), typeof(L2), typeof(S2), typeof(sk), typeof(V), + typeof(skmp)}(pr, kt, L2, S2, sk, V, skmp) + end +end +function HighOrderPrior(; pr::AbstractPriorResult, + kt::Union{Nothing, <:AbstractMatrix} = nothing, + L2::Union{Nothing, <:AbstractMatrix} = nothing, + S2::Union{Nothing, <:AbstractMatrix} = nothing, + sk::Union{Nothing, <:AbstractMatrix} = nothing, + V::Union{Nothing, <:AbstractMatrix} = nothing, + skmp::Union{Nothing, <:AbstractMatrixProcessingEstimator} = nothing) + return HighOrderPrior(pr, kt, L2, S2, sk, V, skmp) +end -export prior +export prior, LowOrderPrior, HighOrderPrior diff --git a/src/13_Prior/2_EmpiricalPrior.jl b/src/13_Prior/2_EmpiricalPrior.jl index 296e58266..988ba4669 100644 --- a/src/13_Prior/2_EmpiricalPrior.jl +++ b/src/13_Prior/2_EmpiricalPrior.jl @@ -1,83 +1,67 @@ -struct LowOrderPrior{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12} <: - AbstractPriorResult - X::T1 - mu::T2 - sigma::T3 - chol::T4 - w::T5 - ens::T6 - kld::T7 - ow::T8 - rr::T9 - f_mu::T10 - f_sigma::T11 - f_w::T12 - function LowOrderPrior(X::AbstractMatrix, mu::AbstractVector, sigma::AbstractMatrix, - chol::Union{Nothing, <:AbstractMatrix}, - w::Union{Nothing, <:AbstractWeights}, - ens::Union{Nothing, <:Real}, - kld::Union{Nothing, <:Real, <:AbstractVector{<:Real}}, - ow::Union{Nothing, <:AbstractVector}, - rr::Union{Nothing, <:Regression}, - f_mu::Union{Nothing, <:AbstractVector}, - f_sigma::Union{Nothing, <:AbstractMatrix}, - f_w::Union{Nothing, <:AbstractVector}) - @argcheck(!isempty(X) && !isempty(mu) && !isempty(sigma)) - @argcheck(size(X, 2) == length(mu)) - assert_matrix_issquare(sigma) - if !isnothing(w) - @argcheck(!isempty(w)) - @argcheck(length(w) == size(X, 1)) - end - if isa(kld, AbstractVector) - @argcheck(!isempty(kld)) - end - if !isnothing(ow) - @argcheck(!isempty(ow)) - end - loadings_flag = !isnothing(rr) - f_mu_flag = !isnothing(f_mu) - f_sigma_flag = !isnothing(f_sigma) - if loadings_flag || f_mu_flag || f_sigma_flag - @argcheck(loadings_flag && f_mu_flag && f_sigma_flag) - @argcheck(!isempty(f_mu) && !isempty(f_sigma)) - assert_matrix_issquare(f_sigma) - @argcheck(size(rr.M, 2) == length(f_mu) == size(f_sigma, 1)) - @argcheck(size(rr.M, 1) == length(mu)) - if !isnothing(chol) - @argcheck(!isempty(chol)) - @argcheck(length(mu) == size(chol, 2)) - end - if !isnothing(f_w) - @argcheck(!isempty(f_w)) - @argcheck(length(f_w) == size(X, 1)) - end - end - return new{typeof(X), typeof(mu), typeof(sigma), typeof(chol), typeof(w), - typeof(ens), typeof(kld), typeof(ow), typeof(rr), typeof(f_mu), - typeof(f_sigma), typeof(f_w)}(X, mu, sigma, chol, w, ens, kld, ow, rr, - f_mu, f_sigma, f_w) - end -end -function LowOrderPrior(; X::AbstractMatrix, mu::AbstractVector, sigma::AbstractMatrix, - chol::Union{Nothing, <:AbstractMatrix} = nothing, - w::Union{Nothing, <:AbstractWeights} = nothing, - ens::Union{Nothing, <:Real} = nothing, - kld::Union{Nothing, <:Real, <:AbstractVector{<:Real}} = nothing, - ow::Union{Nothing, <:AbstractVector} = nothing, - rr::Union{Nothing, <:Regression} = nothing, - f_mu::Union{Nothing, <:AbstractVector} = nothing, - f_sigma::Union{Nothing, <:AbstractMatrix} = nothing, - f_w::Union{Nothing, <:AbstractVector} = nothing) - return LowOrderPrior(X, mu, sigma, chol, w, ens, kld, ow, rr, f_mu, f_sigma, f_w) -end -function prior_view(pr::LowOrderPrior, i::AbstractVector) - chol = isnothing(pr.chol) ? nothing : view(pr.chol, :, i) - return LowOrderPrior(; X = view(pr.X, :, i), mu = view(pr.mu, i), - sigma = view(pr.sigma, i, i), chol = chol, w = pr.w, ens = pr.ens, - kld = pr.kld, ow = pr.ow, rr = regression_view(pr.rr, i), - f_mu = pr.f_mu, f_sigma = pr.f_sigma, f_w = pr.f_w) +""" +```julia +struct EmpiricalPrior{T1, T2, T3} <: AbstractLowOrderPriorEstimator_A + ce::T1 + me::T2 + horizon::T3 end +``` + +Empirical prior estimator for asset returns. + +`EmpiricalPrior` is a low order prior estimator that computes the mean and covariance of asset returns using empirical (sample-based) statistics. It supports custom expected returns and covariance estimators, as well as an optional investment horizon for log-normalisation and scaling. + +# Fields + + - `ce`: Covariance estimator. + - `me`: Expected returns estimator. + - `horizon`: Optional investment horizon. + +# Constructor + +```julia +EmpiricalPrior(; ce::StatsBase.CovarianceEstimator = PortfolioOptimisersCovariance(), + me::AbstractExpectedReturnsEstimator = SimpleExpectedReturns(), + horizon::Union{Nothing, <:Real} = nothing) +``` + +Keyword arguments correspond to the fields above. + +## Validation + + - If `horizon` is not `nothing`, `horizon > 0`. + +# Examples + +```jldoctest +julia> EmpiricalPrior() +EmpiricalPrior + ce | PortfolioOptimisersCovariance + | ce | Covariance + | | me | SimpleExpectedReturns + | | | w | nothing + | | ce | GeneralWeightedCovariance + | | | ce | StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true) + | | | w | nothing + | | alg | Full() + | mp | DefaultMatrixProcessing + | | pdm | Posdef + | | | alg | UnionAll: NearestCorrelationMatrix.Newton + | | denoise | nothing + | | detone | nothing + | | alg | nothing + me | SimpleExpectedReturns + | w | nothing + horizon | nothing +``` + +# Related + + - [`AbstractLowOrderPriorEstimator_A`](@ref) + - [`SimpleExpectedReturns`](@ref) + - [`PortfolioOptimisersCovariance`](@ref) + - [`prior`](@ref) +""" struct EmpiricalPrior{T1, T2, T3} <: AbstractLowOrderPriorEstimator_A ce::T1 me::T2 @@ -85,6 +69,9 @@ struct EmpiricalPrior{T1, T2, T3} <: AbstractLowOrderPriorEstimator_A function EmpiricalPrior(ce::StatsBase.CovarianceEstimator, me::AbstractExpectedReturnsEstimator, horizon::Union{Nothing, <:Real}) + if !isnothing(horizon) + @argcheck(horizon > 0) + end return new{typeof(ce), typeof(me), typeof(horizon)}(ce, me, horizon) end end @@ -98,6 +85,38 @@ function factory(pe::EmpiricalPrior, w::Union{Nothing, <:AbstractWeights} = noth return EmpiricalPrior(; me = factory(pe.me, w), ce = factory(pe.ce, w), horizon = pe.horizon) end +""" +```julia +prior(pe::EmpiricalPrior{<:Any, <:Any, Nothing}, X::AbstractMatrix, args...; dims::Int = 1, + kwargs...) +``` + +Compute empirical prior moments for asset returns (no horizon adjustment). + +`prior` estimates the mean and covariance of asset returns using the specified empirical prior estimator, without log-normalisation or scaling for investment horizon. The mean and covariance are computed using the estimators stored in `pe`, and returned in a [`LowOrderPrior`](@ref) result. + +# Arguments + + - `pe`: Empirical prior estimator. + - `X`: Asset returns matrix (observations × assets). + - `args...`: Additional positional arguments (ignored). + - `dims`: Dimension along which to compute moments. + - `kwargs...`: Additional keyword arguments passed to mean and covariance estimators. + +# Returns + + - `pr::LowOrderPrior`: Result object containing asset returns, mean vector, and covariance matrix. + +# Validation + + - `dims in (1, 2)`. + +# Related + + - [`EmpiricalPrior`](@ref) + - [`LowOrderPrior`](@ref) + - [`prior`](@ref) +""" function prior(pe::EmpiricalPrior{<:Any, <:Any, Nothing}, X::AbstractMatrix, args...; dims::Int = 1, kwargs...) @argcheck(dims in (1, 2)) @@ -108,6 +127,38 @@ function prior(pe::EmpiricalPrior{<:Any, <:Any, Nothing}, X::AbstractMatrix, arg sigma = cov(pe.ce, X; kwargs...) return LowOrderPrior(; X = X, mu = mu, sigma = sigma) end +""" +```julia +prior(pe::EmpiricalPrior{<:Any, <:Any, <:Real}, X::AbstractMatrix, args...; dims::Int = 1, + kwargs...) +``` + +Compute empirical prior moments for asset returns with investment horizon adjustment. + +`prior` estimates the mean and covariance of asset returns using the specified empirical prior estimator, applying log-normalisation and scaling for the investment horizon. The asset returns are log-transformed, moments are computed using the estimators stored in `pe`, and then rescaled according to the investment horizon. The final mean and covariance are transformed back to arithmetic returns and returned in a [`LowOrderPrior`](@ref) result. + +# Arguments + + - `pe`: Empirical prior estimator. + - `X`: Asset returns matrix (observations × assets). + - `args...`: Additional positional arguments (ignored). + - `dims`: Dimension along which to compute moments. + - `kwargs...`: Additional keyword arguments passed to mean and covariance estimators. + +# Returns + + - `pr::LowOrderPrior`: Result object containing asset returns, mean vector, and covariance matrix. + +# Validation + + - `dims in (1, 2)`. + +# Related + + - [`EmpiricalPrior`](@ref) + - [`LowOrderPrior`](@ref) + - [`prior`](@ref) +""" function prior(pe::EmpiricalPrior{<:Any, <:Any, <:Real}, X::AbstractMatrix, args...; dims::Int = 1, kwargs...) @argcheck(dims in (1, 2)) @@ -125,4 +176,4 @@ function prior(pe::EmpiricalPrior{<:Any, <:Any, <:Real}, X::AbstractMatrix, args return LowOrderPrior(; X = X, mu = mu, sigma = sigma) end -export EmpiricalPrior, LowOrderPrior +export EmpiricalPrior diff --git a/src/13_Prior/3_FactorPrior.jl b/src/13_Prior/3_FactorPrior.jl index af75a0ba3..81120c804 100644 --- a/src/13_Prior/3_FactorPrior.jl +++ b/src/13_Prior/3_FactorPrior.jl @@ -1,3 +1,88 @@ +""" +```julia +struct FactorPrior{T1, T2, T3, T4, T5} <: AbstractLowOrderPriorEstimator_F + pe::T1 + mp::T2 + re::T3 + ve::T4 + rsd::T5 +end +``` + +Factor-based prior estimator for asset returns. + +`FactorPrior` is a low order prior estimator that computes the mean and covariance of asset returns using a factor model. It combines a factor prior estimator, matrix post-processing, regression, and variance estimation to produce posterior moments. Optionally, it can add residual variance to the posterior covariance for robust estimation. + +# Fields + + - `pe`: Factor prior estimator. + - `mp`: Matrix post-processing estimator. + - `re`: Regression estimator. + - `ve`: Variance estimator for residuals. + - `rsd`: Boolean flag to add residual variance to posterior covariance. + +# Constructor + +```julia +FactorPrior(; pe::AbstractLowOrderPriorEstimator_A_AF = EmpiricalPrior(), + mp::AbstractMatrixProcessingEstimator = DefaultMatrixProcessing(), + re::AbstractRegressionEstimator = StepwiseRegression(), + ve::AbstractVarianceEstimator = SimpleVariance(), rsd::Bool = true) +``` + +Keyword arguments correspond to the fields above. + +# Examples + +```jldoctest +julia> FactorPrior() +FactorPrior + pe | EmpiricalPrior + | ce | PortfolioOptimisersCovariance + | | ce | Covariance + | | | me | SimpleExpectedReturns + | | | | w | nothing + | | | ce | GeneralWeightedCovariance + | | | | ce | StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true) + | | | | w | nothing + | | | alg | Full() + | | mp | DefaultMatrixProcessing + | | | pdm | Posdef + | | | | alg | UnionAll: NearestCorrelationMatrix.Newton + | | | denoise | nothing + | | | detone | nothing + | | | alg | nothing + | me | SimpleExpectedReturns + | | w | nothing + | horizon | nothing + mp | DefaultMatrixProcessing + | pdm | Posdef + | | alg | UnionAll: NearestCorrelationMatrix.Newton + | denoise | nothing + | detone | nothing + | alg | nothing + re | StepwiseRegression + | crit | PValue + | | threshold | Float64: 0.05 + | alg | Forward() + | target | LinearModel + | | kwargs | @NamedTuple{}: NamedTuple() + ve | SimpleVariance + | me | SimpleExpectedReturns + | | w | nothing + | w | nothing + | corrected | Bool: true + rsd | Bool: true +``` + +# Related + + - [`AbstractLowOrderPriorEstimator_F`](@ref) + - [`EmpiricalPrior`](@ref) + - [`StepwiseRegression`](@ref) + - [`SimpleVariance`](@ref) + - [`prior`](@ref) +""" struct FactorPrior{T1, T2, T3, T4, T5} <: AbstractLowOrderPriorEstimator_F pe::T1 mp::T2 @@ -31,6 +116,38 @@ function Base.getproperty(obj::FactorPrior, sym::Symbol) getfield(obj, sym) end end +""" +```julia +prior(pe::FactorPrior, X::AbstractMatrix, F::AbstractMatrix; dims::Int = 1, kwargs...) +``` + +Compute factor-based prior moments for asset returns using a factor model. + +`prior` estimates the mean and covariance of asset returns using the specified factor prior estimator, regression, and matrix post-processing. The factor returns matrix `F` is used to compute factor moments, which are then mapped to asset space via regression. Optionally, residual variance is added to the posterior covariance for robust estimation. The result is returned as a [`LowOrderPrior`](@ref) object. + +# Arguments + + - `pe`: Factor prior estimator. + - `X`: Asset returns matrix (observations × assets). + - `F`: Factor returns matrix (observations × factors). + - `dims`: Dimension along which to compute moments. + - `kwargs...`: Additional keyword arguments passed to matrix processing and estimators. + +# Returns + + - `pr::LowOrderPrior`: Result object containing posterior asset returns, mean vector, covariance matrix, Cholesky factor, regression result, and factor moments. + +# Validation + + - `dims in (1, 2)`. + +# Related + + - [`FactorPrior`](@ref) + - [`LowOrderPrior`](@ref) + - [`EmpiricalPrior`](@ref) + - [`prior`](@ref) +""" function prior(pe::FactorPrior, X::AbstractMatrix, F::AbstractMatrix; dims::Int = 1, kwargs...) @argcheck(dims in (1, 2)) diff --git a/src/13_Prior/4_HighOrderPrior.jl b/src/13_Prior/4_HighOrderPrior.jl index 5e929b2c0..ff365a202 100644 --- a/src/13_Prior/4_HighOrderPrior.jl +++ b/src/13_Prior/4_HighOrderPrior.jl @@ -1,4 +1,45 @@ -function block_vec_pq(A, p, q) +""" +```julia +block_vec_pq(A::AbstractMatrix, p::Integer, q::Integer) +``` + +Block vectorisation operator. + +`block_vec_pq` transforms a matrix `A` into a block vectorised form, partitioning `A` into blocks of size `(p, q)` and stacking the vectorised blocks row-wise. This is useful for higher-order moment computations and tensor manipulations in portfolio analytics. + +# Arguments + + - `A`: Input matrix of size `(m * p, n * q)`, where `m` and `n` are integers. + - `p`: Number of rows in each block. + - `q`: Number of columns in each block. + +# Returns + + - `A_vec::Matrix`: Block vectorised matrix of size `(m * n, p * q)`. + +# Validation + + - `size(A, 1)` must be an integer multiple of `p`. + - `size(A, 2)` must be an integer multiple of `q`. + +# Examples + +```jldoctest +julia> A = [1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16]; + +julia> PortfolioOptimisers.block_vec_pq(A, 2, 2) +4×4 Matrix{Int64}: + 1 5 2 6 + 9 13 10 14 + 3 7 4 8 + 11 15 12 16 +``` + +# Related + + - [`dup_elim_sum_matrices`](@ref) +""" +function block_vec_pq(A::AbstractMatrix, p::Integer, q::Integer) mp, nq = size(A) if !(mod(mp, p) == 0 && mod(nq, q) == 0) @@ -74,7 +115,6 @@ function duplication_matrix(n::Int, diag::Bool = true) push!(filtered_cols, cols[v[i]]) end end - sparse(filtered_rows, filtered_cols, 1, nsq, m) end end @@ -150,6 +190,71 @@ function summation_matrix(n::Int, diag::Bool = true) end end # COV_EXCL_STOP +""" +```julia +dup_elim_sum_matrices(n::Int) +``` + +Construct duplication, elimination, and summation matrices for symmetric matrix vectorisation. + +`dup_elim_sum_matrices` returns the duplication matrix `D`, elimination matrix `L`, and summation matrix `S` for symmetric matrices of size `n × n`. These matrices are used in higher-order moment computations, tensor manipulations, and efficient vectorisation of symmetric matrices in portfolio analytics. + +# Arguments + + - `n`: Size of the symmetric matrix (integer). + +# Returns + + - `(D, L, S)`: Tuple of three `SparseMatrixCSC{Int64, Int64}` sparse matrices: + + + `D`: Duplication matrix (`n^2 × m`), where `m = n(n+1)/2`. + + `L`: Elimination matrix (`m × n^2`). + + `S`: Summation matrix (`m × n^2`). + +# Validation + + - `n` must be a positive integer. + +# Examples + +```jldoctest +julia> D, L, S = PortfolioOptimisers.dup_elim_sum_matrices(3); + +julia> D +9×6 SparseArrays.SparseMatrixCSC{Int64, Int64} with 9 stored entries: + 1 ⋅ ⋅ ⋅ ⋅ ⋅ + ⋅ 1 ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ 1 ⋅ ⋅ ⋅ + ⋅ 1 ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ 1 ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ 1 ⋅ + ⋅ ⋅ 1 ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ 1 ⋅ + ⋅ ⋅ ⋅ ⋅ ⋅ 1 + +julia> L +6×9 SparseArrays.SparseMatrixCSC{Int64, Int64} with 6 stored entries: + 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ + ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 + +julia> S +6×9 SparseArrays.SparseMatrixCSC{Int64, Int64} with 6 stored entries: + 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ + ⋅ 2 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ 2 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ ⋅ 2 ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 +``` + +# Related + + - [`block_vec_pq`](@ref) +""" function dup_elim_sum_matrices(n::Int) m = div(n * (n + 1), 2) nsq = n^2 @@ -183,57 +288,6 @@ function dup_elim_sum_matrices(n::Int) return d, l, s end -struct HighOrderPrior{T1, T2, T3, T4, T5, T6, T7} <: AbstractPriorResult - pr::T1 - kt::T2 - L2::T3 - S2::T4 - sk::T5 - V::T6 - skmp::T7 - function HighOrderPrior(pr::AbstractPriorResult, kt::Union{Nothing, <:AbstractMatrix}, - L2::Union{Nothing, <:AbstractMatrix}, - S2::Union{Nothing, <:AbstractMatrix}, - sk::Union{Nothing, <:AbstractMatrix}, - V::Union{Nothing, <:AbstractMatrix}, - skmp::Union{Nothing, <:AbstractMatrixProcessingEstimator}) - kt_flag = isa(kt, AbstractMatrix) - L2_flag = isa(L2, AbstractMatrix) - S2_flag = isa(S2, AbstractMatrix) - if kt_flag || L2_flag || S2_flag - @argcheck(kt_flag && L2_flag && S2_flag) - @argcheck(!isempty(kt) && !isempty(L2) && !isempty(S2)) - assert_matrix_issquare(kt) - N = length(pr.mu) - @argcheck(length(pr.mu)^2 == size(kt, 1)) - @argcheck(size(L2) == size(S2) == (div(N * (N + 1), 2), N^2)) - end - sk_flag = isa(sk, AbstractMatrix) - V_flag = isa(V, AbstractMatrix) - if sk_flag - @argcheck(!isempty(sk)) - @argcheck(length(pr.mu)^2 == size(sk, 2)) - end - if V_flag - @argcheck(!isempty(V)) - assert_matrix_issquare(V) - end - if sk_flag || V_flag - @argcheck(sk_flag && V_flag, - "If either sk or V, is nothing, both must be nothing.") - end - return new{typeof(pr), typeof(kt), typeof(L2), typeof(S2), typeof(sk), typeof(V), - typeof(skmp)}(pr, kt, L2, S2, sk, V, skmp) - end -end -function HighOrderPrior(; pr::AbstractPriorResult, kt::Union{Nothing, <:AbstractMatrix}, - L2::Union{Nothing, <:AbstractMatrix}, - S2::Union{Nothing, <:AbstractMatrix}, - sk::Union{Nothing, <:AbstractMatrix}, - V::Union{Nothing, <:AbstractMatrix}, - skmp::Union{Nothing, <:AbstractMatrixProcessingEstimator}) - return HighOrderPrior(pr, kt, L2, S2, sk, V, skmp) -end function dup_elim_sum_view(args...) return nothing, nothing, nothing end @@ -275,6 +329,94 @@ function Base.getproperty(obj::HighOrderPrior, sym::Symbol) getfield(obj, sym) end end +""" +```julia +struct HighOrderPriorEstimator{T1, T2, T3} <: AbstractHighOrderPriorEstimator + pe::T1 + kte::T2 + ske::T3 +end +``` + +High order prior estimator for asset returns. + +`HighOrderPriorEstimator` is a composite estimator that computes high order moments (coskewness and cokurtosis) for asset returns, in addition to low order moments (mean and covariance). It combines a low order prior estimator, a cokurtosis estimator, and a coskewness estimator to produce a [`HighOrderPrior`](@ref) result containing all relevant moments for advanced portfolio analytics. + +# Fields + + - `pe`: Low order prior estimator (`AbstractLowOrderPriorEstimator_A_F_AF`). + - `kte`: Cokurtosis estimator (`CokurtosisEstimator` or `Nothing`). + - `ske`: Coskewness estimator (`CoskewnessEstimator` or `Nothing`). + +# Constructor + +```julia +HighOrderPriorEstimator(; pe::AbstractLowOrderPriorEstimator_A_F_AF = EmpiricalPrior(), + kte::Union{Nothing, <:CokurtosisEstimator} = Cokurtosis(; + alg = Full()), + ske::Union{Nothing, <:CoskewnessEstimator} = Coskewness(; + alg = Full())) +``` + +Keyword arguments correspond to the fields above. + +## Validation + + - All estimators must be valid and subtype the appropriate abstract types. + - If provided, `kte` and `ske` must be compatible with the input data. + +# Examples + +```jldoctest +julia> HighOrderPriorEstimator() +HighOrderPriorEstimator + pe | EmpiricalPrior + | ce | PortfolioOptimisersCovariance + | | ce | Covariance + | | | me | SimpleExpectedReturns + | | | | w | nothing + | | | ce | GeneralWeightedCovariance + | | | | ce | StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true) + | | | | w | nothing + | | | alg | Full() + | | mp | DefaultMatrixProcessing + | | | pdm | Posdef + | | | | alg | UnionAll: NearestCorrelationMatrix.Newton + | | | denoise | nothing + | | | detone | nothing + | | | alg | nothing + | me | SimpleExpectedReturns + | | w | nothing + | horizon | nothing + kte | Cokurtosis + | me | SimpleExpectedReturns + | | w | nothing + | mp | DefaultMatrixProcessing + | | pdm | Posdef + | | | alg | UnionAll: NearestCorrelationMatrix.Newton + | | denoise | nothing + | | detone | nothing + | | alg | nothing + | alg | Full() + ske | Coskewness + | me | SimpleExpectedReturns + | | w | nothing + | mp | NonPositiveDefiniteMatrixProcessing + | | denoise | nothing + | | detone | nothing + | | alg | nothing + | alg | Full() +``` + +# Related + + - [`AbstractHighOrderPriorEstimator`](@ref) + - [`HighOrderPrior`](@ref) + - [`EmpiricalPrior`](@ref) + - [`CokurtosisEstimator`](@ref) + - [`CoskewnessEstimator`](@ref) + - [`prior`](@ref) +""" struct HighOrderPriorEstimator{T1, T2, T3} <: AbstractHighOrderPriorEstimator pe::T1 kte::T2 @@ -307,6 +449,38 @@ function Base.getproperty(obj::HighOrderPriorEstimator, sym::Symbol) getfield(obj, sym) end end +""" + prior(pe::HighOrderPriorEstimator, X::AbstractMatrix, F::Union{Nothing, <:AbstractMatrix} = nothing; dims::Int = 1, kwargs...) + +Compute high order prior moments for asset returns using a composite estimator. + +`prior` estimates the mean, covariance, coskewness, and cokurtosis of asset returns using the specified high order prior estimator. It first computes low order moments (mean and covariance) using the embedded prior estimator, then computes coskewness and cokurtosis tensors using the provided coskewness and cokurtosis estimators. Optionally, factor returns `F` can be provided for factor-based estimation. The result is returned as a [`HighOrderPrior`](@ref) object. + +# Arguments + + - `pe`: High order prior estimator (`HighOrderPriorEstimator`). + - `X`: Asset returns matrix (observations × assets). + - `F`: Optional factor returns matrix (observations × factors). + - `dims`: Dimension along which to compute moments. + - `kwargs...`: Additional keyword arguments passed to underlying estimators. + +# Returns + + - `pr::HighOrderPrior`: Result object containing asset returns, mean vector, covariance matrix, coskewness tensor, cokurtosis tensor, and related quantities. + +# Validation + + - `dims in (1, 2)`. + +# Related + + - [`HighOrderPriorEstimator`](@ref) + - [`HighOrderPrior`](@ref) + - [`EmpiricalPrior`](@ref) + - [`CokurtosisEstimator`](@ref) + - [`CoskewnessEstimator`](@ref) + - [`prior`](@ref) +""" function prior(pe::HighOrderPriorEstimator, X::AbstractMatrix, F::Union{Nothing, <:AbstractMatrix} = nothing; dims::Int = 1, kwargs...) @argcheck(dims in (1, 2)) @@ -325,4 +499,4 @@ function prior(pe::HighOrderPriorEstimator, X::AbstractMatrix, skmp = isnothing(sk) ? nothing : pe.ske.mp) end -export HighOrderPrior, HighOrderPriorEstimator +export HighOrderPriorEstimator diff --git a/src/13_Prior/5_BlackLittermanViewsGeneration.jl b/src/13_Prior/5_BlackLittermanViewsGeneration.jl index 145bcca6b..60de5c46b 100644 --- a/src/13_Prior/5_BlackLittermanViewsGeneration.jl +++ b/src/13_Prior/5_BlackLittermanViewsGeneration.jl @@ -38,8 +38,8 @@ function get_black_litterman_views(lcs::Union{<:ParsingResult, end At += Ai * c end - @argcheck(any(x -> !iszero(x), At), - DomainError("At least one entry in At must be non-zero:\nany(x -> !iszero(x), At) => $(any(x -> !iszero(x), At))")) + @argcheck(any(!iszero, At), + DomainError("At least one entry in At must be non-zero:\nany(!iszero, At) => $(any(!iszero, At))")) append!(P, At) append!(Q, lc.rhs) end diff --git a/src/19_Optimisation/10_MeanRisk.jl b/src/19_Optimisation/10_MeanRisk.jl index 0f2cb9605..6b41c171c 100644 --- a/src/19_Optimisation/10_MeanRisk.jl +++ b/src/19_Optimisation/10_MeanRisk.jl @@ -218,9 +218,9 @@ function solve_mean_risk!(model::JuMP.Model, mr::MeanRisk, ret::JuMPReturnsEstim end function optimise!(mr::MeanRisk, rd::ReturnsResult = ReturnsResult(); dims::Int = 1, str_names::Bool = false, save::Bool = true, kwargs...) - (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, slt, sst, sgmtx, sglt, sgst, nplg, cplg, tn, fees, ret) = processed_jump_optimiser_attributes(mr.opt, - rd; - dims = dims) + (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, slt, sst, sgmtx, sglt, sgst, plg, tn, fees, ret) = processed_jump_optimiser_attributes(mr.opt, + rd; + dims = dims) model = JuMP.Model() set_string_names_on_creation(model, str_names) set_model_scales!(model, mr.opt.sc, mr.opt.so) @@ -230,20 +230,19 @@ function optimise!(mr::MeanRisk, rd::ReturnsResult = ReturnsResult(); dims::Int set_linear_weight_constraints!(model, lcs, :lcs_ineq, :lcs_eq) set_linear_weight_constraints!(model, cent, :cent_ineq, :cent_eq) set_linear_weight_constraints!(model, mr.opt.lcm, :lcm_ineq, :lcm_eq) - set_mip_constraints!(model, wb, mr.opt.card, gcard, nplg, cplg, lt, st, fees, mr.opt.ss) + set_mip_constraints!(model, wb, mr.opt.card, gcard, plg, lt, st, fees, mr.opt.ss) set_smip_constraints!(model, wb, mr.opt.scard, sgcard, smtx, sgmtx, slt, sst, sglt, sgst, mr.opt.ss) set_turnover_constraints!(model, tn) - set_tracking_error_constraints!(model, pr, mr.opt.te, mr, nplg, cplg, fees; rd = rd) + set_tracking_error_constraints!(model, pr, mr.opt.te, mr, plg, fees; rd = rd) set_number_effective_assets!(model, mr.opt.nea) set_l1_regularisation!(model, mr.opt.l1) set_l2_regularisation!(model, mr.opt.l2) set_non_fixed_fees!(model, fees) - set_risk_constraints!(model, mr.r, mr, pr, nplg, cplg, fees; rd = rd) + set_risk_constraints!(model, mr.r, mr, pr, plg, fees; rd = rd) scalarise_risk_expression!(model, mr.opt.sce) set_return_constraints!(model, ret, mr.obj, pr; rd = rd) - set_sdp_phylogeny_constraints!(model, nplg, :sdp_nplg) - set_sdp_phylogeny_constraints!(model, cplg, :sdp_cplg) + set_sdp_phylogeny_constraints!(model, plg) add_custom_constraint!(model, mr.opt.ccnt, mr, pr) retcode, sol = solve_mean_risk!(model, mr, ret, pr, Val(haskey(model, :ret_frontier)), Val(haskey(model, :risk_frontier)), fees) @@ -251,9 +250,8 @@ function optimise!(mr::MeanRisk, rd::ReturnsResult = ReturnsResult(); dims::Int JuMPOptimisation(typeof(mr), ProcessedJuMPOptimiserAttributes(pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, sgmtx, slt, sst, - sglt, sgst, nplg, cplg, tn, fees, - ret), retcode, sol, - ifelse(save, model, nothing)) + sglt, sgst, plg, tn, fees, ret), + retcode, sol, ifelse(save, model, nothing)) else @warn("Using fallback method. Please ignore previous optimisation failure warnings.") optimise!(mr.fallback, rd; dims = dims, str_names = str_names, save = save, diff --git a/src/19_Optimisation/11_FactorRiskContribution.jl b/src/19_Optimisation/11_FactorRiskContribution.jl index f17213e72..f7e0d0753 100644 --- a/src/19_Optimisation/11_FactorRiskContribution.jl +++ b/src/19_Optimisation/11_FactorRiskContribution.jl @@ -1,24 +1,24 @@ -struct FactorRiskContribution{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10} <: +struct FactorRiskContribution{T1, T2, T3, T4, T5, T6, T7, T8, T9} <: JuMPOptimisationEstimator opt::T1 re::T2 r::T3 obj::T4 - nplg::T5 - cplg::T6 - sets::T7 - wi::T8 - flag::T9 - fallback::T10 + plg::T5 + sets::T6 + wi::T7 + flag::T8 + fallback::T9 function FactorRiskContribution(opt::JuMPOptimiser, re::Union{<:Regression, <:AbstractRegressionEstimator}, r::Union{<:RiskMeasure, <:AbstractVector{<:RiskMeasure}}, obj::ObjectiveFunction, - nplg::Union{Nothing, <:SemiDefinitePhylogenyEstimator, - <:SemiDefinitePhylogeny}, - cplg::Union{Nothing, <:SemiDefinitePhylogenyEstimator, - <:SemiDefinitePhylogeny}, + plg::Union{Nothing, + <:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult, + <:Union{<:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult}}, sets::Union{Nothing, <:AssetSets}, wi::Union{Nothing, <:AbstractVector{<:Real}}, flag::Bool, @@ -29,45 +29,37 @@ struct FactorRiskContribution{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10} <: if isa(wi, AbstractVector) @argcheck(!isempty(wi)) end - @argcheck(!isa(opt.nplg, - Union{<:SemiDefinitePhylogenyEstimator, <:SemiDefinitePhylogeny})) - @argcheck(!isa(opt.cplg, - Union{<:SemiDefinitePhylogenyEstimator, <:SemiDefinitePhylogeny})) - return new{typeof(opt), typeof(re), typeof(r), typeof(obj), typeof(nplg), - typeof(cplg), typeof(sets), typeof(wi), typeof(flag), typeof(fallback)}(opt, - re, - r, - obj, - nplg, - cplg, - sets, - wi, - flag, - fallback) + # @argcheck(!isa(opt.plg, + # Union{<:SemiDefinitePhylogenyEstimator, <:SemiDefinitePhylogeny})) + return new{typeof(opt), typeof(re), typeof(r), typeof(obj), typeof(plg), + typeof(sets), typeof(wi), typeof(flag), typeof(fallback)}(opt, re, r, + obj, plg, sets, + wi, flag, + fallback) end end function FactorRiskContribution(; opt::JuMPOptimiser = JuMPOptimiser(), re::Union{<:Regression, <:AbstractRegressionEstimator} = StepwiseRegression(), r::Union{<:RiskMeasure, <:AbstractVector{<:RiskMeasure}} = Variance(), obj::ObjectiveFunction = MinimumRisk(), - nplg::Union{Nothing, <:SemiDefinitePhylogenyEstimator, - <:SemiDefinitePhylogeny} = nothing, - cplg::Union{Nothing, <:SemiDefinitePhylogenyEstimator, - <:SemiDefinitePhylogeny} = nothing, + plg::Union{Nothing, <:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult, + <:Union{<:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult}} = nothing, sets::Union{Nothing, <:AssetSets} = nothing, wi::Union{Nothing, <:AbstractVector{<:Real}} = nothing, flag::Bool = true, fallback::Union{Nothing, <:OptimisationEstimator} = nothing) - return FactorRiskContribution(opt, re, r, obj, nplg, cplg, sets, wi, flag, fallback) + return FactorRiskContribution(opt, re, r, obj, plg, sets, wi, flag, fallback) end function opt_view(frc::FactorRiskContribution, i::AbstractVector, X::AbstractMatrix) X = isa(frc.opt.pe, AbstractPriorResult) ? frc.opt.pe.X : X opt = opt_view(frc.opt, i, X) re = regression_view(frc.re, i) r = risk_measure_view(frc.r, i, X) - return FactorRiskContribution(; opt = opt, re = re, r = r, obj = frc.obj, - nplg = frc.nplg, cplg = frc.cplg, sets = frc.sets, - wi = frc.wi, flag = frc.flag, fallback = frc.fallback) + return FactorRiskContribution(; opt = opt, re = re, r = r, obj = frc.obj, plg = frc.plg, + sets = frc.sets, wi = frc.wi, flag = frc.flag, + fallback = frc.fallback) end function set_factor_risk_contribution_constraints!(model::JuMP.Model, re::Union{<:Regression, @@ -95,9 +87,9 @@ function set_factor_risk_contribution_constraints!(model::JuMP.Model, end function optimise!(frc::FactorRiskContribution, rd::ReturnsResult = ReturnsResult(); dims::Int = 1, str_names::Bool = false, save::Bool = true, kwargs...) - (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, slt, sst, sgmtx, sglt, sgst, nplg, cplg, tn, fees, ret) = processed_jump_optimiser_attributes(frc.opt, - rd; - dims = dims) + (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, slt, sst, sgmtx, sglt, sgst, plg, tn, fees, ret) = processed_jump_optimiser_attributes(frc.opt, + rd; + dims = dims) model = JuMP.Model() set_string_names_on_creation(model, str_names) set_model_scales!(model, frc.opt.sc, frc.opt.so) @@ -107,24 +99,20 @@ function optimise!(frc::FactorRiskContribution, rd::ReturnsResult = ReturnsResul set_linear_weight_constraints!(model, lcs, :lcs_ineq, :lcs_eq) set_linear_weight_constraints!(model, cent, :cent_ineq, :cent_eq) set_linear_weight_constraints!(model, frc.opt.lcm, :lcm_ineq, :lcm_eq) - set_mip_constraints!(model, wb, frc.opt.card, gcard, nplg, cplg, lt, st, fees, - frc.opt.ss) + set_mip_constraints!(model, wb, frc.opt.card, gcard, plg, lt, st, fees, frc.opt.ss) set_smip_constraints!(model, wb, frc.opt.scard, sgcard, smtx, sgmtx, slt, sst, sglt, sgst, frc.opt.ss) set_turnover_constraints!(model, tn) - set_tracking_error_constraints!(model, pr, frc.opt.te, frc, nplg, cplg, fees, b1; - rd = rd) + set_tracking_error_constraints!(model, pr, frc.opt.te, frc, plg, fees, b1; rd = rd) set_number_effective_assets!(model, frc.opt.nea) set_l1_regularisation!(model, frc.opt.l1) set_l2_regularisation!(model, frc.opt.l2) set_non_fixed_fees!(model, fees) - set_risk_constraints!(model, frc.r, frc, pr, nplg, cplg, fees, b1; rd = rd) + set_risk_constraints!(model, frc.r, frc, pr, plg, fees, b1; rd = rd) scalarise_risk_expression!(model, frc.opt.sce) set_return_constraints!(model, ret, frc.obj, pr; rd = rd) - frc_nplg = phylogeny_constraints(frc.nplg, rd.F) - frc_cplg = phylogeny_constraints(frc.cplg, rd.F) - set_sdp_frc_phylogeny_constraints!(model, frc_nplg, :sdp_frc_nplg) - set_sdp_frc_phylogeny_constraints!(model, frc_cplg, :sdp_frc_cplg) + frc_plg = phylogeny_constraints(frc.plg, rd.F) + set_sdp_frc_phylogeny_constraints!(model, frc_plg) add_custom_constraint!(model, frc.opt.ccnt, frc, pr) set_portfolio_objective_function!(model, frc.obj, ret, frc.opt.cobj, frc, pr) retcode, sol = optimise_JuMP_model!(model, frc, eltype(pr.X)) @@ -137,10 +125,9 @@ function optimise!(frc::FactorRiskContribution, rd::ReturnsResult = ReturnsResul smtx, sgmtx, slt, sst, sglt, sgst, - nplg, cplg, - tn, fees, - ret), rr, - frc_nplg, frc_cplg, retcode, sol, + plg, tn, + fees, ret), + rr, frc_plg, retcode, sol, ifelse(save, model, nothing)) else @warn("Using fallback method. Please ignore previous optimisation failure warnings.") diff --git a/src/19_Optimisation/12_NearOptimalCentering.jl b/src/19_Optimisation/12_NearOptimalCentering.jl index 5ceab8fd0..cbb7d0e81 100644 --- a/src/19_Optimisation/12_NearOptimalCentering.jl +++ b/src/19_Optimisation/12_NearOptimalCentering.jl @@ -547,10 +547,8 @@ function optimise!(noc::NearOptimalCentering{<:Any, <:Any, <:Any, <:Any, <:Any, opt.sgmtx, opt.slt, opt.sst, opt.sglt, - opt.sgst, - opt.nplg, - opt.cplg, opt.tn, - opt.fees, + opt.sgst, opt.plg, + opt.tn, opt.fees, opt.ret), w_min_retcode, w_opt_retcode, w_max_retcode, noc_retcode, retcode, sol, @@ -578,22 +576,20 @@ function optimise!(noc::NearOptimalCentering{<:Any, <:Any, <:Any, <:Any, <:Any, set_linear_weight_constraints!(model, opt.lcs, :lcs_ineq, :lcs_eq) set_linear_weight_constraints!(model, opt.cent, :cent_ineq, :cent_eq) set_linear_weight_constraints!(model, opt.lcm, :lcm_ineq, :lcm_eq) - set_mip_constraints!(model, opt.wb, opt.card, opt.gcard, opt.nplg, opt.cplg, opt.lt, - opt.st, opt.fees, opt.ss) + set_mip_constraints!(model, opt.wb, opt.card, opt.gcard, opt.plg, opt.lt, opt.st, + opt.fees, opt.ss) set_smip_constraints!(model, opt.wb, opt.scard, opt.sgcard, opt.smtx, opt.sgmtx, opt.slt, opt.sst, opt.sglt, nothing, opt.ss) set_turnover_constraints!(model, opt.tn) - set_tracking_error_constraints!(model, opt.pe, opt.te, noc, opt.nplg, opt.cplg, - opt.fees; rd = rd) + set_tracking_error_constraints!(model, opt.pe, opt.te, noc, opt.plg, opt.fees; rd = rd) set_number_effective_assets!(model, opt.nea) set_l1_regularisation!(model, opt.l1) set_l2_regularisation!(model, opt.l2) set_non_fixed_fees!(model, opt.fees) - set_risk_constraints!(model, r, noc, opt.pe, opt.nplg, opt.cplg, opt.fees; rd = rd) + set_risk_constraints!(model, r, noc, opt.pe, opt.plg, opt.fees; rd = rd) scalarise_risk_expression!(model, opt.sce) set_return_constraints!(model, opt.ret, MinimumRisk(), opt.pe; rd = rd) - set_sdp_phylogeny_constraints!(model, opt.nplg, :sdp_nplg) - set_sdp_phylogeny_constraints!(model, opt.cplg, :sdp_cplg) + set_sdp_phylogeny_constraints!(model, opt.plg) add_custom_constraint!(model, opt.ccnt, opt, opt.pe) noc_retcode, sol = solve_noc!(noc, model, rk_opt, rt_opt, opt, rt_min, rt_max, w_min, w_max, Val(haskey(model, :ret_frontier)), @@ -610,10 +606,8 @@ function optimise!(noc::NearOptimalCentering{<:Any, <:Any, <:Any, <:Any, <:Any, opt.sgmtx, opt.slt, opt.sst, opt.sglt, - opt.sgst, - opt.nplg, - opt.cplg, opt.tn, - opt.fees, + opt.sgst, opt.plg, + opt.tn, opt.fees, opt.ret), w_min_retcode, w_opt_retcode, w_max_retcode, noc_retcode, retcode, sol, diff --git a/src/19_Optimisation/13_RiskBudgeting.jl b/src/19_Optimisation/13_RiskBudgeting.jl index 412707054..ad0f124eb 100644 --- a/src/19_Optimisation/13_RiskBudgeting.jl +++ b/src/19_Optimisation/13_RiskBudgeting.jl @@ -116,9 +116,9 @@ function set_risk_budgeting_constraints!(model::JuMP.Model, end function optimise!(rb::RiskBudgeting, rd::ReturnsResult = ReturnsResult(); dims::Int = 1, str_names::Bool = false, save::Bool = true, kwargs...) - (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, slt, sst, sgmtx, sglt, sgst, nplg, cplg, tn, fees, ret) = processed_jump_optimiser_attributes(rb.opt, - rd; - dims = dims) + (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, slt, sst, sgmtx, sglt, sgst, plg, tn, fees, ret) = processed_jump_optimiser_attributes(rb.opt, + rd; + dims = dims) model = JuMP.Model() set_string_names_on_creation(model, str_names) set_model_scales!(model, rb.opt.sc, rb.opt.so) @@ -126,20 +126,19 @@ function optimise!(rb::RiskBudgeting, rd::ReturnsResult = ReturnsResult(); dims: set_linear_weight_constraints!(model, lcs, :lcs_ineq, :lcs_eq) set_linear_weight_constraints!(model, cent, :cent_ineq, :cent_eq) set_linear_weight_constraints!(model, rb.opt.lcm, :lcm_ineq, :lcm_eq) - set_mip_constraints!(model, wb, rb.opt.card, gcard, nplg, cplg, lt, st, fees, rb.opt.ss) + set_mip_constraints!(model, wb, rb.opt.card, gcard, plg, lt, st, fees, rb.opt.ss) set_smip_constraints!(model, wb, rb.opt.scard, sgcard, smtx, sgmtx, slt, sst, sglt, sgst, rb.opt.ss) set_turnover_constraints!(model, tn) - set_tracking_error_constraints!(model, pr, rb.opt.te, rb, nplg, cplg, fees; rd = rd) + set_tracking_error_constraints!(model, pr, rb.opt.te, rb, plg, fees; rd = rd) set_number_effective_assets!(model, rb.opt.nea) set_l1_regularisation!(model, rb.opt.l1) set_l2_regularisation!(model, rb.opt.l2) set_non_fixed_fees!(model, fees) - set_risk_constraints!(model, rb.r, rb, pr, nplg, cplg, fees; rd = rd) + set_risk_constraints!(model, rb.r, rb, pr, plg, fees; rd = rd) scalarise_risk_expression!(model, rb.opt.sce) set_return_constraints!(model, ret, MinimumRisk(), pr; rd = rd) - set_sdp_phylogeny_constraints!(model, nplg, :sdp_nplg) - set_sdp_phylogeny_constraints!(model, cplg, :sdp_cplg) + set_sdp_phylogeny_constraints!(model, plg) add_custom_constraint!(model, rb.opt.ccnt, rb, pr) set_portfolio_objective_function!(model, MinimumRisk(), ret, rb.opt.cobj, rb, pr) retcode, sol = optimise_JuMP_model!(model, rb, eltype(pr.X)) @@ -148,10 +147,9 @@ function optimise!(rb::RiskBudgeting, rd::ReturnsResult = ReturnsResult(); dims: ProcessedJuMPOptimiserAttributes(pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, sgmtx, slt, - sst, sglt, sgst, - nplg, cplg, tn, fees, - ret), prb, retcode, - sol, ifelse(save, model, nothing)) + sst, sglt, sgst, plg, + tn, fees, ret), prb, + retcode, sol, ifelse(save, model, nothing)) else @warn("Using fallback method. Please ignore previous optimisation failure warnings.") optimise!(rb.fallback, rd; dims = dims, str_names = str_names, save = save, diff --git a/src/19_Optimisation/14_RelaxedRiskBudgeting.jl b/src/19_Optimisation/14_RelaxedRiskBudgeting.jl index 6358f3cce..e655134d5 100644 --- a/src/19_Optimisation/14_RelaxedRiskBudgeting.jl +++ b/src/19_Optimisation/14_RelaxedRiskBudgeting.jl @@ -151,9 +151,9 @@ function set_relaxed_risk_budgeting_constraints!(model::JuMP.Model, end function optimise!(rrb::RelaxedRiskBudgeting, rd::ReturnsResult = ReturnsResult(); dims::Int = 1, str_names::Bool = false, save::Bool = true, kwargs...) - (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, slt, sst, sgmtx, sglt, sgst, nplg, cplg, tn, fees, ret) = processed_jump_optimiser_attributes(rrb.opt, - rd; - dims = dims) + (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, slt, sst, sgmtx, sglt, sgst, plg, tn, fees, ret) = processed_jump_optimiser_attributes(rrb.opt, + rd; + dims = dims) model = JuMP.Model() set_string_names_on_creation(model, str_names) set_model_scales!(model, rrb.opt.sc, rrb.opt.so) @@ -162,8 +162,7 @@ function optimise!(rrb::RelaxedRiskBudgeting, rd::ReturnsResult = ReturnsResult( set_linear_weight_constraints!(model, lcs, :lcs_ineq, :lcs_eq) set_linear_weight_constraints!(model, cent, :cent_ineq, :cent_eq) set_linear_weight_constraints!(model, rrb.opt.lcm, :lcm_ineq, :lcm_eq) - set_mip_constraints!(model, wb, rrb.opt.card, gcard, nplg, cplg, lt, st, fees, - rrb.opt.ss) + set_mip_constraints!(model, wb, rrb.opt.card, gcard, plg, lt, st, fees, rrb.opt.ss) set_smip_constraints!(model, wb, rrb.opt.scard, sgcard, smtx, sgmtx, slt, sst, sglt, sgst, rrb.opt.ss) set_turnover_constraints!(model, tn) @@ -174,8 +173,7 @@ function optimise!(rrb::RelaxedRiskBudgeting, rd::ReturnsResult = ReturnsResult( set_l2_regularisation!(model, rrb.opt.l2) set_non_fixed_fees!(model, fees) set_return_constraints!(model, ret, MinimumRisk(), pr; rd = rd) - set_sdp_phylogeny_constraints!(model, nplg, :sdp_nplg) - set_sdp_phylogeny_constraints!(model, cplg, :sdp_cplg) + set_sdp_phylogeny_constraints!(model, plg) add_custom_constraint!(model, rrb.opt.ccnt, rrb, pr) set_portfolio_objective_function!(model, MinimumRisk(), ret, rrb.opt.cobj, rrb, pr) retcode, sol = optimise_JuMP_model!(model, rrb, eltype(pr.X)) @@ -184,10 +182,9 @@ function optimise!(rrb::RelaxedRiskBudgeting, rd::ReturnsResult = ReturnsResult( ProcessedJuMPOptimiserAttributes(pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, sgmtx, slt, - sst, sglt, sgst, - nplg, cplg, tn, fees, - ret), prb, retcode, - sol, ifelse(save, model, nothing)) + sst, sglt, sgst, plg, + tn, fees, ret), prb, + retcode, sol, ifelse(save, model, nothing)) else @warn("Using fallback method. Please ignore previous optimisation failure warnings.") optimise!(rrb.fallback, rd; dims = dims, str_names = str_names, save = save, diff --git a/src/19_Optimisation/15_NestedClustering.jl b/src/19_Optimisation/15_NestedClustering.jl index 941b053a9..1ad9cbde0 100644 --- a/src/19_Optimisation/15_NestedClustering.jl +++ b/src/19_Optimisation/15_NestedClustering.jl @@ -38,8 +38,9 @@ function assert_internal_optimiser(opt::JuMPOptimisationEstimator) @argcheck(!isa(opt.opt.gcard, LinearConstraint)) @argcheck(!isa(opt.opt.sgcard, LinearConstraint)) # @argcheck(!isa(opt.opt.smtx, AbstractMatrix)) - @argcheck(!isa(opt.opt.nplg, AbstractPhylogenyConstraintResult)) - @argcheck(!isa(opt.opt.cplg, AbstractPhylogenyConstraintResult)) + @argcheck(!isa(opt.opt.plg, AbstractPhylogenyConstraintResult) || + isa(opt.opt.plg, AbstractVector) && + !any(x -> isa(x, AbstractPhylogenyConstraintResult), opt.opt.plg)) return nothing end function assert_internal_optimiser(opt::AbstractVector{<:Union{<:OptimisationEstimator, diff --git a/src/19_Optimisation/17_RiskConstraints.jl b/src/19_Optimisation/17_RiskConstraints.jl index d7a54fd32..73cef1da6 100644 --- a/src/19_Optimisation/17_RiskConstraints.jl +++ b/src/19_Optimisation/17_RiskConstraints.jl @@ -101,14 +101,12 @@ function sdp_rc_variance_flag!(::JuMP.Model, return true end function sdp_variance_flag!(model::JuMP.Model, rc_flag::Bool, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}) + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}) return if rc_flag || haskey(model, :rc_variance) || - isa(cplg, SemiDefinitePhylogeny) || - isa(nplg, SemiDefinitePhylogeny) + isa(plg, SemiDefinitePhylogeny) || + isa(plg, AbstractVector) && any(x -> isa(x, SemiDefinitePhylogeny), plg) true else false @@ -203,13 +201,13 @@ end function set_risk!(model::JuMP.Model, i::Any, r::Variance, opt::Union{<:MeanRisk, <:NearOptimalCentering, <:RiskBudgeting}, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, args...; kwargs...) rc = linear_constraints(r.rc, opt.opt.sets; datatype = eltype(pr.X), strict = opt.opt.strict) rc_flag = sdp_rc_variance_flag!(model, opt, rc) - sdp_flag = sdp_variance_flag!(model, rc_flag, cplg, nplg) + sdp_flag = sdp_variance_flag!(model, rc_flag, plg) key = Symbol(:variance_risk_, i) variance_risk = set_variance_risk!(model, i, r, pr, sdp_flag, key) rc_variance_constraints!(model, i, rc, variance_risk) @@ -218,15 +216,13 @@ end function set_risk_constraints!(model::JuMP.Model, i::Any, r::Variance, opt::Union{<:MeanRisk, <:NearOptimalCentering, <:RiskBudgeting}, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, args...; kwargs...) + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, + args...; kwargs...) if !haskey(model, :variance_flag) @expression(model, variance_flag, true) end - variance_risk, sdp_flag = set_risk!(model, i, r, opt, pr, cplg, nplg, args...; - kwargs...) + variance_risk, sdp_flag = set_risk!(model, i, r, opt, pr, plg, args...; kwargs...) var_bound_expr, var_bound_key = variance_risk_bounds_expr(model, i, sdp_flag) ub = variance_risk_bounds_val(sdp_flag, r.settings.ub) set_variance_risk_bounds_and_expression!(model, opt, var_bound_expr, ub, var_bound_key, @@ -235,7 +231,7 @@ function set_risk_constraints!(model::JuMP.Model, i::Any, r::Variance, end function set_risk_constraints!(model::JuMP.Model, i::Any, r::Variance, opt::FactorRiskContribution, pr::AbstractPriorResult, ::Any, - ::Any, ::Any, b1::AbstractMatrix, args...; kwargs...) + ::Any, b1::AbstractMatrix, args...; kwargs...) if !haskey(model, :variance_flag) @expression(model, variance_flag, true) end @@ -243,7 +239,7 @@ function set_risk_constraints!(model::JuMP.Model, i::Any, r::Variance, strict = opt.opt.strict) key = Symbol(:variance_risk_, i) set_sdp_frc_constraints!(model) - W = model[:W] + W = model[:frc_W] sigma = isnothing(r.sigma) ? pr.sigma : r.sigma sigma_W = model[Symbol(:sigma_W_, i)] = @expression(model, transpose(b1) * sigma * b1 * W) @@ -2100,24 +2096,20 @@ function set_risk_constraints!(model::JuMP.Model, i::Any, end function set_risk_tr_constraints!(key::Any, model::JuMP.Model, r::RiskMeasure, opt::JuMPOptimisationEstimator, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) - return set_risk_constraints!(model, Symbol(key, 1), r, opt, pr, cplg, nplg, fees, - args...; kwargs...) + return set_risk_constraints!(model, Symbol(key, 1), r, opt, pr, plg, fees, args...; + kwargs...) end function set_risk_tr_constraints!(key::Any, model::JuMP.Model, rs::AbstractVector{<:RiskMeasure}, opt::JuMPOptimisationEstimator, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) for (i, r) in enumerate(rs) - set_risk_constraints!(model, Symbol(key, i), r, opt, pr, cplg, nplg, fees, args...; + set_risk_constraints!(model, Symbol(key, i), r, opt, pr, plg, fees, args...; kwargs...) end return nothing @@ -2125,10 +2117,8 @@ end function set_triv_risk_constraints!(model::JuMP.Model, i::Any, r::RiskMeasure, opt::Union{<:MeanRisk, <:NearOptimalCentering, <:RiskBudgeting}, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) variance_flag = haskey(model, :variance_flag) rc_variance = haskey(model, :rc_variance) @@ -2242,8 +2232,8 @@ function set_triv_risk_constraints!(model::JuMP.Model, i::Any, r::RiskMeasure, unregister(model, :bdvariance_risk) end - risk_expr = set_risk_tr_constraints!(Symbol(:triv_, i, :_), model, r, opt, pr, cplg, - nplg, fees, args...; kwargs...) + risk_expr = set_risk_tr_constraints!(Symbol(:triv_, i, :_), model, r, opt, pr, plg, + fees, args...; kwargs...) if !variance_flag && haskey(model, :variance_flag) || haskey(model, :oldvariance_flag) model[Symbol(:trdv_, i, :_variance_flag)] = model[:variance_flag] @@ -2460,10 +2450,8 @@ function set_risk_constraints!(model::JuMP.Model, i::Any, <:IndependentVariableTracking}, opt::Union{<:MeanRisk, <:NearOptimalCentering, <:RiskBudgeting}, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) key = Symbol(:tracking_risk_, i) ri = r.r @@ -2473,8 +2461,8 @@ function set_risk_constraints!(model::JuMP.Model, i::Any, model[:oldw] = model[:w] unregister(model, :w) model[:w] = @expression(model, w - wb * k) - tracking_risk = set_triv_risk_constraints!(model, i, ri, opt, pr, cplg, nplg, fees, - args...; kwargs...) + tracking_risk = set_triv_risk_constraints!(model, i, ri, opt, pr, plg, fees, args...; + kwargs...) model[Symbol(:triv_, i, :_w)] = model[:w] model[:w] = model[:oldw] unregister(model, :oldw) @@ -2484,10 +2472,8 @@ end function set_trdv_risk_constraints!(model::JuMP.Model, i::Any, r::RiskMeasure, opt::Union{<:MeanRisk, <:NearOptimalCentering, <:RiskBudgeting}, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) variance_flag = haskey(model, :variance_flag) rc_variance = haskey(model, :rc_variance) @@ -2527,8 +2513,8 @@ function set_trdv_risk_constraints!(model::JuMP.Model, i::Any, r::RiskMeasure, unregister(model, :ceucs_variance) end - risk_expr = set_risk_tr_constraints!(Symbol(:trdv_, i, :_), model, r, opt, pr, cplg, - nplg, fees, args...; kwargs...) + risk_expr = set_risk_tr_constraints!(Symbol(:trdv_, i, :_), model, r, opt, pr, plg, + fees, args...; kwargs...) if !variance_flag && haskey(model, :variance_flag) || haskey(model, :oldvariance_flag) model[Symbol(:trdv_, i, :_variance_flag)] = model[:variance_flag] @@ -2606,10 +2592,8 @@ function set_risk_constraints!(model::JuMP.Model, i::Any, <:DependentVariableTracking}, opt::Union{<:MeanRisk, <:NearOptimalCentering, <:RiskBudgeting}, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) key = Symbol(:tracking_risk_, i) ri = r.r @@ -2618,7 +2602,7 @@ function set_risk_constraints!(model::JuMP.Model, i::Any, k = model[:k] sc = model[:sc] tracking_risk = model[key] = @variable(model) - risk_expr = set_trdv_risk_constraints!(model, i, ri, opt, pr, cplg, nplg, fees, args...; + risk_expr = set_trdv_risk_constraints!(model, i, ri, opt, pr, plg, fees, args...; kwargs...) dr = model[Symbol(:rdr_, i)] = @expression(model, risk_expr - rb * k) model[Symbol(:crtr_noc_, i)] = @constraint(model, diff --git a/src/19_Optimisation/6_Base_JuMPOptimisation.jl b/src/19_Optimisation/6_Base_JuMPOptimisation.jl index 9ea28021b..e023930cb 100644 --- a/src/19_Optimisation/6_Base_JuMPOptimisation.jl +++ b/src/19_Optimisation/6_Base_JuMPOptimisation.jl @@ -147,23 +147,19 @@ function set_risk_constraints!(args...; kwargs...) end function set_risk_constraints!(model::JuMP.Model, r::RiskMeasure, opt::JuMPOptimisationEstimator, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) - set_risk_constraints!(model, 1, r, opt, pr, cplg, nplg, fees, args...; kwargs...) + set_risk_constraints!(model, 1, r, opt, pr, plg, fees, args...; kwargs...) return nothing end function set_risk_constraints!(model::JuMP.Model, rs::AbstractVector{<:RiskMeasure}, opt::JuMPOptimisationEstimator, pr::AbstractPriorResult, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) for (i, r) in enumerate(rs) - set_risk_constraints!(model, i, r, opt, pr, cplg, nplg, fees, args...; kwargs...) + set_risk_constraints!(model, i, r, opt, pr, plg, fees, args...; kwargs...) end return nothing end diff --git a/src/19_Optimisation/7_Returns_and_ObjectiveFunctions.jl b/src/19_Optimisation/7_Returns_and_ObjectiveFunctions.jl index 0495e6212..1a8eca7ff 100644 --- a/src/19_Optimisation/7_Returns_and_ObjectiveFunctions.jl +++ b/src/19_Optimisation/7_Returns_and_ObjectiveFunctions.jl @@ -56,6 +56,132 @@ end function no_bounds_returns_estimator(r::KellyReturn, args...) return KellyReturn(; w = r.w) end +#= +mutable struct AKelly <: RetType + formulation::VarianceFormulation + a_rc::Union{<:AbstractMatrix, Nothing} + b_rc::Union{<:AbstractVector, Nothing} +end +function AKelly(; formulation::VarianceFormulation = SOC(), + a_rc::Union{<:AbstractMatrix, Nothing} = nothing, + b_rc::Union{<:AbstractVector, Nothing} = nothing) + if !isnothing(a_rc) && !isnothing(b_rc) && !isempty(a_rc) && !isempty(b_rc) + @smart_assert(size(a_rc, 1) == length(b_rc)) + end + return AKelly(formulation, a_rc, b_rc) +end +function Base.setproperty!(obj::AKelly, sym::Symbol, val) + if sym == :a_rc + if !isnothing(val) && !isnothing(obj.b_rc) && !isempty(val) && !isempty(obj.b_rc) + @smart_assert(size(val, 1) == length(obj.b_rc)) + end + elseif sym == :b_rc + if !isnothing(val) && !isnothing(obj.a_rc) && !isempty(val) && !isempty(obj.a_rc) + @smart_assert(size(obj.a_rc, 1) == length(val)) + end + end + return setfield!(obj, sym, val) +end +function set_objective_function(port, ::Sharpe, ::Union{AKelly, EKelly}, custom_obj) + model = port.model + scale_obj = model[:scale_obj] + ret = model[:ret] + @expression(model, obj_func, ret) + add_objective_penalty(model, obj_func, -1) + custom_objective(port, obj_func, -1, custom_obj) + @objective(model, Max, scale_obj * obj_func) + return nothing +end +function return_constraints(port, type, ::Any, kelly::AKelly, mu, sigma, returns, + kelly_approx_idx) + if isempty(mu) + return nothing + end + + model = port.model + get_fees(model) + w = model[:w] + fees = model[:fees] + if isnothing(kelly_approx_idx) || + isempty(kelly_approx_idx) || + iszero(kelly_approx_idx[1]) + if !haskey(model, :variance_risk) + a_rc = kelly.a_rc + b_rc = kelly.b_rc + sdp_rc_variance(model, type, a_rc, b_rc) + calc_variance_risk(get_ntwk_clust_type(port, a_rc, b_rc), kelly.formulation, + model, mu, sigma, returns) + end + variance_risk = model[:variance_risk] + @expression(model, ret, dot(mu, w) - fees - 0.5 * variance_risk) + else + variance_risk = model[:variance_risk] + @expression(model, ret, + dot(mu, w) - fees - 0.5 * variance_risk[kelly_approx_idx[1]]) + end + + return_bounds(port) + + return nothing +end +function return_constraints(port, type, obj::Sharpe, kelly::AKelly, mu, sigma, returns, + kelly_approx_idx) + a_rc = kelly.a_rc + b_rc = kelly.b_rc + sdp_rc_variance(port.model, type, a_rc, b_rc) + return_sharpe_akelly_constraints(port, type, obj, kelly, + get_ntwk_clust_type(port, a_rc, b_rc), mu, sigma, + returns, kelly_approx_idx) + return nothing +end +function return_sharpe_akelly_constraints(port, type, obj::Sharpe, kelly::AKelly, + adjacency_constraint::Union{NoAdj, IP}, mu, sigma, + returns, kelly_approx_idx) + if isempty(mu) + return nothing + end + + model = port.model + get_fees(model) + scale_constr = model[:scale_constr] + w = model[:w] + k = model[:k] + fees = model[:fees] + ohf = model[:ohf] + risk = model[:risk] + rf = obj.rf + @variable(model, tapprox_kelly) + @constraint(model, constr_sr_akelly_risk, scale_constr * risk <= scale_constr * ohf) + @expression(model, ret, dot(mu, w) - fees - 0.5 * tapprox_kelly - k * rf) + if isnothing(kelly_approx_idx) || + isempty(kelly_approx_idx) || + iszero(kelly_approx_idx[1]) + if !haskey(model, :variance_risk) + calc_variance_risk(adjacency_constraint, kelly.formulation, model, mu, sigma, + returns) + end + dev = model[:dev] + @constraint(model, constr_sr_akelly_ret, + [scale_constr * (k + tapprox_kelly) + scale_constr * 2 * dev + scale_constr * (k - tapprox_kelly)] ∈ SecondOrderCone()) + else + dev = model[:dev] + @constraint(model, constr_sr_akelly_ret, + [scale_constr * (k + tapprox_kelly) + scale_constr * 2 * dev[kelly_approx_idx[1]] + scale_constr * (k - tapprox_kelly)] ∈ SecondOrderCone()) + end + return_bounds(port) + + return nothing +end +function return_sharpe_akelly_constraints(port, type, obj::Sharpe, ::AKelly, ::SDP, ::Any, + ::Any, returns, ::Any) + return_constraints(port, type, obj, EKelly(), nothing, nothing, returns, nothing) + return nothing +end +=# for r in traverse_concrete_subtypes(JuMPReturnsEstimator) eval(quote function bounds_returns_estimator(r::$(r), lb::Real) diff --git a/src/19_Optimisation/8_JuMPConstraints.jl b/src/19_Optimisation/8_JuMPConstraints.jl index ca7762d71..b402ba41f 100644 --- a/src/19_Optimisation/8_JuMPConstraints.jl +++ b/src/19_Optimisation/8_JuMPConstraints.jl @@ -566,18 +566,33 @@ function mip_constraints(model::JuMP.Model, wb::WeightBounds, end return ib end +function set_iplg_constraints!(model::JuMP.Model, + plgs::Union{<:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}) + ib = model[:ib] + sc = model[:sc] + for (i, plg) in enumerate(plgs) + if !isa(plg, IntegerPhylogeny) + continue + end + A = plg.A + B = plg.B + model[Symbol(:card_plg_, i)] = @constraint(model, sc * (A * ib ⊖ B) <= 0) + end + return nothing +end function set_mip_constraints!(model::JuMP.Model, wb::WeightBounds, card::Union{Nothing, <:Integer}, gcard::Union{Nothing, <:LinearConstraint}, - nplg::Union{Nothing, <:AbstractPhylogenyConstraintResult}, - cplg::Union{Nothing, <:AbstractPhylogenyConstraintResult}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, lt::Union{Nothing, <:BuyInThreshold}, st::Union{Nothing, <:BuyInThreshold}, fees::Union{Nothing, <:Fees}, ss::Union{Nothing, <:Real}) card_flag = !isnothing(card) gcard_flag = !isnothing(gcard) - n_flag = isa(nplg, IntegerPhylogeny) - c_flag = isa(cplg, IntegerPhylogeny) + iplg_flag = isa(plg, IntegerPhylogeny) || + isa(plg, AbstractVector) && any(x -> isa(x, IntegerPhylogeny), plg) lt_flag = !isnothing(lt) st_flag = !isnothing(st) ffl_flag, ffs_flag, ffl, ffs = if !isnothing(fees) @@ -585,14 +600,7 @@ function set_mip_constraints!(model::JuMP.Model, wb::WeightBounds, else false, false, nothing, nothing end - if !(card_flag || - gcard_flag || - n_flag || - c_flag || - lt_flag || - st_flag || - ffl_flag || - ffs_flag) + if !(card_flag || gcard_flag || iplg_flag || lt_flag || st_flag || ffl_flag || ffs_flag) return nothing end ib = if (st_flag || ffl_flag || ffs_flag) && haskey(model, :sw) @@ -617,15 +625,8 @@ function set_mip_constraints!(model::JuMP.Model, wb::WeightBounds, @constraint(model, gcard_eq, sc * (A * ib ⊖ B) == 0) end end - if n_flag - A = nplg.A - B = nplg.B - @constraint(model, card_nplg, sc * (A * ib ⊖ B) <= 0) - end - if c_flag - A = cplg.A - B = cplg.B - @constraint(model, card_cplg, sc * (A * ib ⊖ B) <= 0) + if iplg_flag + set_iplg_constraints!(model, plg) end return nothing end @@ -1074,10 +1075,9 @@ function set_tracking_error_constraints!(model::JuMP.Model, i::Integer, te::RiskTrackingError{<:Any, <:Any, <:Any, <:IndependentVariableTracking}, opt::JuMPOptimisationEstimator, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, + <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) r = te.r wb = te.tracking.w @@ -1089,8 +1089,8 @@ function set_tracking_error_constraints!(model::JuMP.Model, i::Integer, model[:oldw] = model[:w] unregister(model, :w) model[:w] = @expression(model, w - wb * k) - risk_expr = set_triv_risk_constraints!(model, te_dw, r, opt, pr, cplg, nplg, fees, - args...; kwargs...) + risk_expr = set_triv_risk_constraints!(model, te_dw, r, opt, pr, plg, fees, args...; + kwargs...) model[Symbol(:triv_, i, :_w)] = model[:w] model[:w] = model[:oldw] unregister(model, :oldw) @@ -1102,10 +1102,9 @@ function set_tracking_error_constraints!(model::JuMP.Model, i::Integer, te::RiskTrackingError{<:Any, <:Any, <:Any, <:DependentVariableTracking}, opt::JuMPOptimisationEstimator, - cplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, - nplg::Union{Nothing, <:SemiDefinitePhylogeny, - <:IntegerPhylogeny}, + plg::Union{Nothing, + <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}, fees::Union{Nothing, <:Fees}, args...; kwargs...) ri = te.r wb = te.tracking.w @@ -1115,8 +1114,8 @@ function set_tracking_error_constraints!(model::JuMP.Model, i::Integer, sc = model[:sc] key = Symbol(:t_dr_, i) t_dr = model[key] = @variable(model) - risk_expr = set_trdv_risk_constraints!(model, key, ri, opt, pr, cplg, nplg, fees, - args...; kwargs...) + risk_expr = set_trdv_risk_constraints!(model, key, ri, opt, pr, plg, fees, args...; + kwargs...) dr = model[Symbol(:dr_, i)] = @expression(model, risk_expr - rb * k) model[Symbol(:cter_noc_, i)], model[Symbol(:cter_, i)] = @constraints(model, begin @@ -1222,50 +1221,67 @@ function set_sdp_constraints!(model::JuMP.Model) return W end function set_sdp_frc_constraints!(model::JuMP.Model) - if haskey(model, :W) - return model[:W] + if haskey(model, :frc_W) + return model[:frc_W] end w1 = model[:w1] sc = model[:sc] k = model[:k] Nf = length(w1) - @variable(model, W[1:Nf, 1:Nf], Symmetric) - @expression(model, M, hcat(vcat(W, transpose(w1)), vcat(w1, k))) - @constraint(model, M_PSD, sc * M in PSDCone()) - return W -end -function set_sdp_phylogeny_constraints!(args...) - return nothing -end -function set_sdp_phylogeny_constraints!(model::JuMP.Model, adj::SemiDefinitePhylogeny, - key::Symbol) + @variable(model, frc_W[1:Nf, 1:Nf], Symmetric) + @expression(model, frc_M, hcat(vcat(frc_W, transpose(w1)), vcat(w1, k))) + @constraint(model, frc_M_PSD, sc * frc_M in PSDCone()) + return frc_W +end +function set_sdp_phylogeny_constraints!(model::JuMP.Model, + plgs::Union{Nothing, + <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}) + if !(isa(plgs, SemiDefinitePhylogeny) || + isa(plgs, AbstractVector) && any(x -> isa(x, SemiDefinitePhylogeny), plgs)) + return nothing + end sc = model[:sc] W = set_sdp_constraints!(model) - A = adj.A - model[key] = @constraint(model, sc * A ⊙ W == 0) - if !haskey(model, :variance_flag) - key = Symbol(key, :_p) - p = adj.p - plp = model[key] = @expression(model, p * tr(W)) - add_to_objective_penalty!(model, plp) + for (i, plg) in enumerate(plgs) + if !isa(plg, SemiDefinitePhylogeny) + continue + end + key = Symbol(:sdp_plg_, i) + A = plg.A + model[key] = @constraint(model, sc * A ⊙ W == 0) + if !haskey(model, :variance_flag) + key = Symbol(key, :_p) + p = plg.p + plp = model[key] = @expression(model, p * tr(W)) + add_to_objective_penalty!(model, plp) + end end return nothing end -function set_sdp_frc_phylogeny_constraints!(args...) - return nothing -end -function set_sdp_frc_phylogeny_constraints!(model::JuMP.Model, adj::SemiDefinitePhylogeny, - key::Symbol) +function set_sdp_frc_phylogeny_constraints!(model::JuMP.Model, + plgs::Union{Nothing, + <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:AbstractPhylogenyConstraintResult}}) + if !(isa(plgs, SemiDefinitePhylogeny) || + isa(plgs, AbstractVector) && any(x -> isa(x, SemiDefinitePhylogeny), plgs)) + return nothing + end sc = model[:sc] - set_sdp_frc_constraints!(model) - W = model[:W] - A = adj.A - model[key] = @constraint(model, sc * A ⊙ W == 0) - if !haskey(model, :variance_flag) - key = Symbol(key, :_p) - p = adj.p - plp = model[key] = @expression(model, p * tr(W)) - add_to_objective_penalty!(model, plp) + W = set_sdp_frc_constraints!(model) + for (i, plg) in enumerate(plgs) + if !isa(plg, SemiDefinitePhylogeny) + continue + end + key = Symbol(:frc_sdp_plg_, i) + A = plg.A + model[key] = @constraint(model, sc * A ⊙ W == 0) + if !haskey(model, :variance_flag) + key = Symbol(key, :_p) + p = plg.p + plp = model[key] = @expression(model, p * tr(W)) + add_to_objective_penalty!(model, plp) + end end return nothing end diff --git a/src/19_Optimisation/9_JuMPOptimiser.jl b/src/19_Optimisation/9_JuMPOptimiser.jl index f0fd6bae4..79f000d7e 100644 --- a/src/19_Optimisation/9_JuMPOptimiser.jl +++ b/src/19_Optimisation/9_JuMPOptimiser.jl @@ -1,5 +1,5 @@ struct ProcessedJuMPOptimiserAttributes{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, - T13, T14, T15, T16, T17, T18, T19} <: AbstractResult + T13, T14, T15, T16, T17, T18} <: AbstractResult pr::T1 wb::T2 lt::T3 @@ -14,11 +14,10 @@ struct ProcessedJuMPOptimiserAttributes{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, sst::T12 sglt::T13 sgst::T14 - nplg::T15 - cplg::T16 - tn::T17 - fees::T18 - ret::T19 + plg::T15 + tn::T16 + fees::T17 + ret::T18 end struct ProcessedFactorRiskBudgetingAttributes{T1, T2, T3} <: AbstractResult rkb::T1 @@ -35,16 +34,15 @@ struct JuMPOptimisation{T1, T2, T3, T4, T5} <: OptimisationResult sol::T4 model::T5 end -struct JuMPOptimisationFactorRiskContribution{T1, T2, T3, T4, T5, T6, T7, T8} <: +struct JuMPOptimisationFactorRiskContribution{T1, T2, T3, T4, T5, T6, T7} <: OptimisationResult oe::T1 pa::T2 rr::T3 - frc_nplg::T4 - frc_cplg::T5 - retcode::T6 - sol::T7 - model::T8 + frc_plg::T4 + retcode::T5 + sol::T6 + model::T7 end struct JuMPOptimisationRiskBudgeting{T1, T2, T3, T4, T5, T6} <: OptimisationResult oe::T1 @@ -66,7 +64,7 @@ end function Base.getproperty(r::JuMPOptimisationFactorRiskContribution, sym::Symbol) return if sym == :w !isa(r.sol, AbstractVector) ? getfield(r.sol, :w) : getfield.(r.sol, :w) - elseif sym in (:oe, :pa, :frc_nplg, :frc_cplg, :retcode, :sol, :model) + elseif sym in (:oe, :pa, :frc_plg, :retcode, :sol, :model) getfield(r, sym) else getfield(r.pa, sym) @@ -93,7 +91,7 @@ function assert_finite_nonnegative_real_or_vec(val::AbstractVector{<:Real}) end struct JuMPOptimiser{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, - T31, T32, T33, T34, T35, T36, T37} <: BaseJuMPOptimisationEstimator + T31, T32, T33, T34, T35, T36} <: BaseJuMPOptimisationEstimator pe::T1 # PriorEstimator slv::T2 wb::T3 # WeightBounds @@ -113,24 +111,23 @@ struct JuMPOptimiser{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 sglt::T17 sgst::T18 sets::T19 - nplg::T20 - cplg::T21 - tn::T22 # Turnover - te::T23 # TrackingError - fees::T24 - ret::T25 - sce::T26 - ccnt::T27 - cobj::T28 - sc::T29 - so::T30 - card::T31 - scard::T32 - nea::T33 - l1::T34 - l2::T35 - ss::T36 - strict::T37 + plg::T20 + tn::T21 # Turnover + te::T22 # TrackingError + fees::T23 + ret::T24 + sce::T25 + ccnt::T26 + cobj::T27 + sc::T28 + so::T29 + card::T30 + scard::T31 + nea::T32 + l1::T33 + l2::T34 + ss::T35 + strict::T36 function JuMPOptimiser(pe::Union{<:AbstractPriorEstimator, <:AbstractPriorResult}, slv::Union{<:Solver, <:AbstractVector{<:Solver}}, wb::Union{Nothing, <:WeightBoundsEstimator, <:WeightBounds}, @@ -171,10 +168,10 @@ struct JuMPOptimiser{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 <:AbstractVector{<:Union{Nothing, <:BuyInThreshold, <:BuyInThresholdEstimator}}}, sets::Union{Nothing, <:AssetSets}, - nplg::Union{Nothing, <:AbstractPhylogenyConstraintEstimator, - <:AbstractPhylogenyConstraintResult}, - cplg::Union{Nothing, <:AbstractPhylogenyConstraintEstimator, - <:AbstractPhylogenyConstraintResult}, + plg::Union{Nothing, <:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:Union{<:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult}}}, tn::Union{Nothing, <:TurnoverEstimator, <:Turnover, <:AbstractVector{<:Union{<:TurnoverEstimator, <:Turnover}}}, @@ -312,15 +309,14 @@ struct JuMPOptimiser{T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 return new{typeof(pe), typeof(slv), typeof(wb), typeof(bgt), typeof(sbgt), typeof(lt), typeof(st), typeof(lcs), typeof(lcm), typeof(cent), typeof(gcard), typeof(sgcard), typeof(smtx), typeof(sgmtx), typeof(slt), - typeof(sst), typeof(sglt), typeof(sgst), typeof(sets), typeof(nplg), - typeof(cplg), typeof(tn), typeof(te), typeof(fees), typeof(ret), - typeof(sce), typeof(ccnt), typeof(cobj), typeof(sc), typeof(so), - typeof(card), typeof(scard), typeof(nea), typeof(l1), typeof(l2), - typeof(ss), typeof(strict)}(pe, slv, wb, bgt, sbgt, lt, st, lcs, lcm, - cent, gcard, sgcard, smtx, sgmtx, slt, sst, - sglt, sgst, sets, nplg, cplg, tn, te, fees, - ret, sce, ccnt, cobj, sc, so, card, scard, - nea, l1, l2, ss, strict) + typeof(sst), typeof(sglt), typeof(sgst), typeof(sets), typeof(plg), + typeof(tn), typeof(te), typeof(fees), typeof(ret), typeof(sce), + typeof(ccnt), typeof(cobj), typeof(sc), typeof(so), typeof(card), + typeof(scard), typeof(nea), typeof(l1), typeof(l2), typeof(ss), + typeof(strict)}(pe, slv, wb, bgt, sbgt, lt, st, lcs, lcm, cent, gcard, + sgcard, smtx, sgmtx, slt, sst, sglt, sgst, sets, plg, tn, + te, fees, ret, sce, ccnt, cobj, sc, so, card, scard, nea, + l1, l2, ss, strict) end end function JuMPOptimiser(; @@ -361,10 +357,10 @@ function JuMPOptimiser(; <:AbstractVector{<:Union{Nothing, <:BuyInThreshold, <:BuyInThresholdEstimator}}} = nothing, sets::Union{Nothing, <:AssetSets} = nothing, - nplg::Union{Nothing, <:AbstractPhylogenyConstraintEstimator, - <:AbstractPhylogenyConstraintResult} = nothing, - cplg::Union{Nothing, <:AbstractPhylogenyConstraintEstimator, - <:AbstractPhylogenyConstraintResult} = nothing, + plg::Union{Nothing, <:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult, + <:AbstractVector{<:Union{<:AbstractPhylogenyConstraintEstimator, + <:AbstractPhylogenyConstraintResult}}} = nothing, tn::Union{Nothing, <:TurnoverEstimator, <:Turnover, <:AbstractVector{<:Union{<:TurnoverEstimator, <:Turnover}}} = nothing, te::Union{Nothing, <:AbstractTracking, @@ -381,8 +377,8 @@ function JuMPOptimiser(; l2::Union{Nothing, <:Real} = nothing, ss::Union{Nothing, <:Real} = nothing, strict::Bool = false) return JuMPOptimiser(pe, slv, wb, bgt, sbgt, lt, st, lcs, lcm, cent, gcard, sgcard, - smtx, sgmtx, slt, sst, sglt, sgst, sets, nplg, cplg, tn, te, fees, - ret, sce, ccnt, cobj, sc, so, card, scard, nea, l1, l2, ss, strict) + smtx, sgmtx, slt, sst, sglt, sgst, sets, plg, tn, te, fees, ret, + sce, ccnt, cobj, sc, so, card, scard, nea, l1, l2, ss, strict) end function opt_view(opt::JuMPOptimiser, i::AbstractVector, X::AbstractMatrix) X = isa(opt.pe, AbstractPriorResult) ? opt.pe.X : X @@ -420,10 +416,10 @@ function opt_view(opt::JuMPOptimiser, i::AbstractVector, X::AbstractMatrix) lt = lt, st = st, lcs = opt.lcs, lcm = opt.lcm, cent = opt.cent, gcard = opt.gcard, sgcard = opt.sgcard, smtx = smtx, sgmtx = sgmtx, slt = slt, sst = sst, sglt = sglt, sgst = sgst, sets = sets, - nplg = opt.nplg, cplg = opt.cplg, tn = tn, te = te, fees = fees, - ret = ret, sce = opt.sce, ccnt = ccnt, cobj = cobj, sc = opt.sc, - so = opt.so, card = opt.card, scard = opt.scard, nea = opt.nea, - l1 = opt.l1, l2 = opt.l2, ss = opt.ss, strict = opt.strict) + plg = opt.plg, tn = tn, te = te, fees = fees, ret = ret, + sce = opt.sce, ccnt = ccnt, cobj = cobj, sc = opt.sc, so = opt.so, + card = opt.card, scard = opt.scard, nea = opt.nea, l1 = opt.l1, + l2 = opt.l2, ss = opt.ss, strict = opt.strict) end function processed_jump_optimiser_attributes(opt::JuMPOptimiser, rd::ReturnsResult; dims::Int = 1) @@ -461,14 +457,12 @@ function processed_jump_optimiser_attributes(opt::JuMPOptimiser, rd::ReturnsResu sgst = threshold_constraints(opt.sgst, opt.sets; datatype = datatype, strict = opt.strict) end - nplg = phylogeny_constraints(opt.nplg, pr.X; iv = rd.iv, ivpa = rd.ivpa) - cplg = phylogeny_constraints(opt.cplg, pr.X; iv = rd.iv, ivpa = rd.ivpa) + plg = phylogeny_constraints(opt.plg, pr.X; iv = rd.iv, ivpa = rd.ivpa) tn = turnover_constraints(opt.tn, opt.sets; strict = opt.strict) fees = fees_constraints(opt.fees, opt.sets; datatype = datatype, strict = opt.strict) ret = jump_returns_factory(opt.ret, pr) return ProcessedJuMPOptimiserAttributes(pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, - sgmtx, slt, sst, sglt, sgst, nplg, cplg, tn, - fees, ret) + sgmtx, slt, sst, sglt, sgst, plg, tn, fees, ret) end function no_bounds_optimiser(opt::JuMPOptimiser, args...) pnames = Tuple(setdiff(propertynames(opt), (:ret,))) @@ -476,17 +470,17 @@ function no_bounds_optimiser(opt::JuMPOptimiser, args...) NamedTuple{pnames}(getproperty.(Ref(opt), pnames))...) end function processed_jump_optimiser(opt::JuMPOptimiser, rd::ReturnsResult; dims::Int = 1) - (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, sgmtx, slt, sst, sglt, sgst, nplg, cplg, tn, fees, ret) = processed_jump_optimiser_attributes(opt, - rd; - dims = dims) + (; pr, wb, lt, st, lcs, cent, gcard, sgcard, smtx, sgmtx, slt, sst, sglt, sgst, plg, tn, fees, ret) = processed_jump_optimiser_attributes(opt, + rd; + dims = dims) return JuMPOptimiser(; pe = pr, slv = opt.slv, wb = wb, bgt = opt.bgt, sbgt = opt.sbgt, lt = lt, st = st, lcs = lcs, lcm = opt.lcm, cent = cent, gcard = gcard, sgcard = sgcard, smtx = smtx, sgmtx = sgmtx, slt = slt, sst = sst, sglt = sglt, sgst = sgst, sets = opt.sets, - nplg = nplg, cplg = cplg, tn = tn, te = opt.te, fees = fees, - ret = ret, sce = opt.sce, ccnt = opt.ccnt, cobj = opt.cobj, - sc = opt.sc, so = opt.so, card = opt.card, nea = opt.nea, - l1 = opt.l1, l2 = opt.l2, ss = opt.ss, strict = opt.strict) + plg = plg, tn = tn, te = opt.te, fees = fees, ret = ret, + sce = opt.sce, ccnt = opt.ccnt, cobj = opt.cobj, sc = opt.sc, + so = opt.so, card = opt.card, nea = opt.nea, l1 = opt.l1, + l2 = opt.l2, ss = opt.ss, strict = opt.strict) end export ProcessedJuMPOptimiserAttributes, JuMPOptimisation, JuMPOptimisationRiskBudgeting, diff --git a/test/test_16_mean_risk_optimisation.jl b/test/test_16_mean_risk_optimisation.jl index a7f155132..64a2d6a1b 100644 --- a/test/test_16_mean_risk_optimisation.jl +++ b/test/test_16_mean_risk_optimisation.jl @@ -1146,11 +1146,11 @@ end @testset "Phylogeny" begin plc = IntegerPhylogenyEstimator(; pe = NetworkEstimator(), B = 1) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, cplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1), l2 = 0.001) res = optimise!(MeanRisk(; obj = MaximumRatio(; rf = rf), opt = opt)) - @test all(value.(res.cplg.A * res.model[:ib]) .<= res.cplg.B) - idx = [BitVector(res.cplg.A[:, i]) for i in axes(res.cplg.A, 2)] + @test all(value.(res.plg.A * res.model[:ib]) .<= res.plg.B) + idx = [BitVector(res.plg.A[:, i]) for i in axes(res.plg.A, 2)] @test all([(count(abs.(getindex(res.w, i)) .> 1e-10) <= 1) for i in idx]) @test isapprox(res.w, [-9.431976408001725e-15, -0.8782741689961527, -9.379228352184268e-15, @@ -1164,11 +1164,11 @@ -3.831552897938868e-15, 0.999998251856358], rtol = 1e-6) plc = IntegerPhylogenyEstimator(; pe = NetworkEstimator(), B = 2) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, nplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1), l2 = 0.0001) res = optimise!(MeanRisk(; obj = MinimumRisk(), opt = opt)) - @test all(value.(res.nplg.A * res.model[:ib]) .<= res.nplg.B) - idx = [BitVector(res.nplg.A[:, i]) for i in axes(res.nplg.A, 2)] + @test all(value.(res.plg.A * res.model[:ib]) .<= res.plg.B) + idx = [BitVector(res.plg.A[:, i]) for i in axes(res.plg.A, 2)] @test all([(count(abs.(getindex(res.w, i)) .> 1e-10) <= 2) for i in idx]) success = isapprox(res.w, [-5.444667507634538e-13, -0.04740153354475791, @@ -1197,29 +1197,29 @@ 0.0696639310927601]; rtol = 1.0e-6) end plc = SemiDefinitePhylogenyEstimator(; pe = clr) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, cplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1)) res = optimise!(MeanRisk(; obj = MinimumRisk(), opt = opt)) - @test isapprox(value.(res.cplg.A .* res.model[:W]), zeros(size(pr.sigma)), + @test isapprox(value.(res.plg.A .* res.model[:W]), zeros(size(pr.sigma)), atol = 1e-10) plc = SemiDefinitePhylogenyEstimator(; pe = ClusteringEstimator(), p = 1000) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, nplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1)) @test isapprox(res.w, optimise!(MeanRisk(; obj = MinimumRisk(), opt = opt)).w) plc = phylogeny_constraints(SemiDefinitePhylogenyEstimator(; pe = clr, p = 10), rd.X) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, cplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1)) @test isapprox(res.w, optimise!(MeanRisk(; obj = MinimumRisk(), opt = opt)).w) plc = SemiDefinitePhylogenyEstimator(; pe = clr) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, cplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1)) res1 = optimise!(MeanRisk(; r = ConditionalValueatRisk(), obj = MaximumRatio(; rf = rf), opt = opt)) - @test isapprox(value.(res1.cplg.A .* res1.model[:W]), zeros(size(pr.sigma)), + @test isapprox(value.(res1.plg.A .* res1.model[:W]), zeros(size(pr.sigma)), atol = 1e-10) @test isapprox(res1.w, [2.630737181170687e-10, -0.19525137327795888, 3.3177497633887114e-10, @@ -1231,11 +1231,11 @@ 1.0651967980173403e-9, 0.5200322956777765], rtol = 1e-6) plc = SemiDefinitePhylogenyEstimator(; pe = clr, p = 5) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, nplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1)) res2 = optimise!(MeanRisk(; r = ConditionalValueatRisk(), obj = MaximumRatio(; rf = rf), opt = opt)) - @test isapprox(value.(res2.nplg.A .* res2.model[:W]), zeros(size(pr.sigma)), + @test isapprox(value.(res2.plg.A .* res2.model[:W]), zeros(size(pr.sigma)), atol = 1e-10) @test !isapprox(res1.w, res2.w; rtol = 0.25) @test isapprox(res2.w, @@ -1249,11 +1249,11 @@ 5.543601714607396e-12, 0.4917295985730075], rtol = 1e-6) plc = SemiDefinitePhylogenyEstimator(; pe = clr) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, cplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1)) res1 = optimise!(MeanRisk(; r = ConditionalValueatRisk(), obj = MaximumUtility(), opt = opt)) - @test isapprox(value.(res1.cplg.A .* res1.model[:W]), zeros(size(pr.sigma)), + @test isapprox(value.(res1.plg.A .* res1.model[:W]), zeros(size(pr.sigma)), atol = 1e-10) @test isapprox(res1.w, [4.427061986438287e-10, -1.5714922493260265e-9, @@ -1266,11 +1266,11 @@ 2.0696138171794054e-9, 0.004277240413999178], rtol = 1e-6) plc = SemiDefinitePhylogenyEstimator(; pe = clr, p = 5) - opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, nplg = plc, + opt = JuMPOptimiser(; pe = pr, slv = mip_slv, sbgt = 1, bgt = 1, plg = plc, wb = WeightBounds(; lb = -1, ub = 1)) res2 = optimise!(MeanRisk(; r = ConditionalValueatRisk(), obj = MaximumUtility(), opt = opt)) - @test isapprox(value.(res2.nplg.A .* res2.model[:W]), zeros(size(pr.sigma)), + @test isapprox(value.(res2.plg.A .* res2.model[:W]), zeros(size(pr.sigma)), atol = 1e-10) @test isapprox(res2.w, [2.17025563455507e-10, 6.087556118806929e-11, 7.438771978806892e-10, diff --git a/test/test_19_nested_clustering_optimisation.jl b/test/test_19_nested_clustering_optimisation.jl index 9df6f3484..6dfcfc59e 100644 --- a/test/test_19_nested_clustering_optimisation.jl +++ b/test/test_19_nested_clustering_optimisation.jl @@ -249,10 +249,10 @@ for (i, opt) in enumerate(opts) res = optimise!(opt, rd) rtol = if i == 2 - 5e-5 + 1e-4 elseif i == 3 5e-4 - elseif i == 4 || Sys.isapple() && i == 12 + elseif i in (4, 12) 5e-6 else 1e-6