Документация Engee

Оптимизация портфеля

Это руководство было изначально предоставлено Арпитом Бхатией (Arpit Bhatia).

Модели оптимизации играют все более важную роль в принятии финансовых решений. Многие вычислительные финансовые задачи можно эффективно решать с помощью современных методов оптимизации.

В этом руководстве решается известная задача оптимизации портфеля Марковица с использованием данных из конспектов лекций, которые читал в Технологическом институте Джорджии Шаббир Ахмед (Shabbir Ahmed).

Требуемые пакеты

В этом руководстве используются следующие пакеты:

using JuMP
import DataFrames
import Ipopt
import MultiObjectiveAlgorithms as MOA
import Plots
import Statistics
import StatsPlots

Формулировка

Предположим, мы рассматриваем возможность инвестирования 1000 долларов в три акции, по которым не выплачиваются дивиденды: IBM (IBM), Walmart (WMT) и Southern Electric (SEHI), сроком на один месяц.

Мы покупаем акции трех компаний по текущим рыночным ценам на первоначальную сумму, удерживаем их в течение одного месяца, а затем в конце месяца продаем по преобладающим рыночным ценам.

Как рациональные инвесторы мы надеемся получить некоторую прибыль от этого начинания, то есть доходность инвестиций должна быть положительной.

Предположим, мы купили акции по цене долларов за акцию в начале месяца и продали их по цене долларов за акцию в конце месяца. Тогда месячная доходность акций составит .

Поскольку цены на акции весьма неопределенны, то же самое касается и доходности инвестиций на конец месяца. Наша цель — инвестировать таким образом, чтобы ожидаемая доходность на конец месяца составляла не менее $50 или 5 %. Кроме того, мы хотим, чтобы «риск» недостижения желаемой доходности был минимален.

Обратите внимание, что задача решается при следующих допущениях:

  1. Торговать можно любым набором акций.

  2. Продажа без покрытия не допускается.

  3. Транзакционных издержек нет.

Эта задача моделируется с переменными решения , которые означают суммы, инвестированные в каждую из трех акций.

Обозначим как случайную величину, соответствующую ежемесячной доходности (приросту цены акций) на доллар для акции .

Тогда доход (или прибыль) от суммы долларов, вложенной в акции , составит , а общий (случайный) доход от инвестиций равен Ожидаемый доход от инвестиций равен Missing or unrecognized delimiter for \right =\sum_{i=1}^{3} \overline{r}_{i} x_{i},], где  — ожидаемое значение

Теперь необходимо количественно оценить понятие «риска» инвестиций.

Марковиц в своей удостоенной Нобелевской премии работе показал, что рациональное представление инвестора о минимизации риска можно близко аппроксимировать путем минимизации дисперсии доходности инвестиционного портфеля. Дисперсия выражается следующей формулой:

где  — ковариация доходности акции с доходностью акции .

Обратите внимание, что правая часть уравнения представляет собой максимально сокращенную форму выражения; мы не стали показывать промежуточные шаги, необходимые для ее вывода. Это уравнение можно также записать в следующей форме:

где  — ковариационная матрица для случайного вектора .

Наконец, модель можно записать так:

Данные

В качестве данных для задачи мы используем приведенные ниже месячные цены акций с ноября 2000 года по ноябрь 2001 года.

df = DataFrames.DataFrame(
    [
        93.043 51.826 1.063
        84.585 52.823 0.938
        111.453 56.477 1.000
        99.525 49.805 0.938
        95.819 50.287 1.438
        114.708 51.521 1.700
        111.515 51.531 2.540
        113.211 48.664 2.390
        104.942 55.744 3.120
        99.827 47.916 2.980
        91.607 49.438 1.900
        107.937 51.336 1.750
        115.590 55.081 1.800
    ],
    [:IBM, :WMT, :SEHI],
)

13×3 DataFrame

RowIBMWMTSEHI
Float64Float64Float64
193.04351.8261.063
284.58552.8230.938
3111.45356.4771.0
499.52549.8050.938
595.81950.2871.438
6114.70851.5211.7
7111.51551.5312.54
8113.21148.6642.39
9104.94255.7443.12
1099.82747.9162.98
1191.60749.4381.9
12107.93751.3361.75
13115.5955.0811.8

Далее вычислим процентную доходность акций за каждый месяц:

returns = diff(Matrix(df); dims = 1) ./ Matrix(df[1:end-1, :])
12×3 Matrix{Float64}:
 -0.0909042   0.0192374    -0.117592
  0.317645    0.0691744     0.0660981
 -0.107023   -0.118137     -0.062
 -0.0372369   0.00967774    0.533049
  0.197132    0.0245391     0.182197
 -0.0278359   0.000194096   0.494118
  0.0152087  -0.0556364    -0.0590551
 -0.0730406   0.145487      0.305439
 -0.0487412  -0.140428     -0.0448718
 -0.0823425   0.0317639    -0.362416
  0.178261    0.0383915    -0.0789474
  0.0709025   0.0729508     0.0285714

Ожидаемая ежемесячная доходность составляет:

r = vec(Statistics.mean(returns; dims = 1))
3-element Vector{Float64}:
 0.026002150277777348
 0.008101316405671459
 0.07371590949198982

Ковариационная матрица имеет следующий вид:

Q = Statistics.cov(returns)
3×3 Matrix{Float64}:
 0.018641    0.00359853  0.00130976
 0.00359853  0.00643694  0.00488727
 0.00130976  0.00488727  0.0686828

Формулировка на языке JuMP

model = Model(Ipopt.Optimizer)
set_silent(model)
@variable(model, x[1:3] >= 0)
@objective(model, Min, x' * Q * x)
@constraint(model, sum(x) <= 1000)
@constraint(model, r' * x >= 50)
optimize!(model)
@assert is_solved_and_feasible(model)
solution_summary(model)
* Solver : Ipopt

* Status
  Result count       : 1
  Termination status : LOCALLY_SOLVED
  Message from the solver:
  "Solve_Succeeded"

* Candidate solution (result #1)
  Primal status      : FEASIBLE_POINT
  Dual status        : FEASIBLE_POINT
  Objective value    : 2.26344e+04
  Dual objective value : 4.52688e+04

* Work counters
  Solve time (sec)   : 5.02205e-03
  Barrier iterations : 11

Оптимальное распределение наших активов:

value.(x)
3-element Vector{Float64}:
 497.04552984986407
  -9.670578501816873e-9
 502.9544801594808

Поэтому мы тратим $497 на акции IBM и $503 на акции SEHI. В результате получается такая дисперсия:

scalar_variance = value(x' * Q * x)
22634.41784988414

и ожидаемая доходность:

scalar_return = value(r' * x)
49.99999950000236

Многоцелевая оптимизация портфеля

Предыдущая модель дала единственное решение с минимизацией дисперсии, гарантирующее ожидаемую прибыль не менее 50.0 На практике может быть допустима немного более высокая дисперсия при условии значительного увеличения ожидаемой доходности. Чтобы исследовать эту область решения задачи, мы можем сформулировать задачу оптимизации портфеля с двумя целями:

  1. минимизация дисперсии;

  2. максимизация ожидаемой доходности.

Решением этой двухцелевой задачи является граница эффективности из современной теории инвестиционного портфеля, а каждая точка решения — это точка с наилучшей доходностью при фиксированном уровне риска.

model = Model(() -> MOA.Optimizer(Ipopt.Optimizer))
set_silent(model)

Необходимо также выбрать алгоритм решения для MOA. Для нашей задачи граница эффективности будет иметь бесконечное число решений. Так как мы не можем найти все решения, выберем алгоритм аппроксимации и ограничим количество возвращаемых точек решения:

set_optimizer_attribute(model, MOA.Algorithm(), MOA.EpsilonConstraint())
set_optimizer_attribute(model, MOA.SolutionLimit(), 50)

Теперь можно определить остальную модель:

@variable(model, x[1:3] >= 0)
@constraint(model, sum(x) <= 1000)
@expression(model, variance, x' * Q * x)
@expression(model, expected_return, r' * x)
# Мы хотим минимизировать дисперсию и максимизировать ожидаемую доходность, но должны выбрать
# единственное целевое назначение `Min` и поменять знак целевых функций с назначением `Max`:
@objective(model, Min, [variance, -expected_return])
optimize!(model)
@assert termination_status(model) == OPTIMAL
solution_summary(model)
* Solver : MOA[algorithm=MultiObjectiveAlgorithms.EpsilonConstraint, optimizer=Ipopt]

* Status
  Result count       : 50
  Termination status : OPTIMAL
  Message from the solver:
  "Solve complete. Found 50 solution(s)"

* Candidate solution (result #1)
  Primal status      : FEASIBLE_POINT
  Dual status        : NO_SOLUTION
  Objective value    : [2.58170e-08,-5.36777e-05]
  Objective bound    : [5.78303e-09,-7.37159e+01]

* Work counters
  Solve time (sec)   : 1.23696e+00
  Barrier iterations : 0

Алгоритм нашел 50 различных решений. Давайте построим графики и посмотрим, чем они отличаются:

objective_space = Plots.hline(
    [scalar_return];
    label = "Single-objective solution",
    linecolor = :red,
)
Plots.vline!(objective_space, [scalar_variance]; label = "", linecolor = :red)
Plots.scatter!(
    objective_space,
    [value(variance; result = i) for i in 1:result_count(model)],
    [value(expected_return; result = i) for i in 1:result_count(model)];
    xlabel = "Variance",
    ylabel = "Expected Return",
    label = "",
    title = "Objective space",
    markercolor = "white",
    markersize = 5,
    legend = :bottomright,
)
for i in 1:result_count(model)
    y = objective_value(model; result = i)
    Plots.annotate!(objective_space, y[1], -y[2], (i, 3))
end

decision_space = StatsPlots.groupedbar(
    vcat([value.(x; result = i)' for i in 1:result_count(model)]...);
    bar_position = :stack,
    label = ["IBM" "WMT" "SEHI"],
    xlabel = "Solution #",
    ylabel = "Investment (\$)",
    title = "Decision space",
)
Plots.plot(objective_space, decision_space; layout = (2, 1), size = (600, 600))

Возможно, компромисс оказался не таким уж и плохим. Наше первоначальное решение соответствует выбору решения № 17. Если купить больше акций SEHI, можно увеличить доходность, но при этом вырастет и дисперсия. Если купить меньше акций SEHI, как в решении № 5 или № 6, можно получить аналогичную прибыль, не вкладывая весь капитал. Также следует отметить, что ни в коем случае не следует покупать акции WMT.