Ребалансировка — это важная часть долгосрочного управления портфелем. По мере изменения цен активов ваш инвестиционный портфель может отклоняться от целевого распределения, что потенциально увеличивает риск и может снизить доходность.
В этой статье я покажу, как контролировать веса активов в портфеле с помощью различных стратегий ребалансировки, используя библиотеку okama
для Python.
Что такое ребалансировка?
Ребалансировка — это процесс восстановления целевого соотношения ценных бумаг в портфеле за счет продажи и покупки. После ребалансировки все активы возвращаются к своим исходным (целевым) весам.
okama
поддерживает несколько стратегий ребалансировки инвестиционного портфеля. Эти стратегии делятся на два типа:
Календарная ребалансировка
Ребалансировка по условию
Календарная ребалансировка
Календарная ребалансировка — это стратегия, которая предполагает восстановление пропорций ценных бумаг через регулярные промежутки времени, например, ежемесячно, ежеквартально или ежегодно.
В окаме доступные следующие периоды для календарных стратегий ребалансировки:
'month' (месяц)
'quarter' (квартал)
'half-year' (полгода)
'year' (год)
Ребалансировка по условию (rebalancing bands)
Стратегия ребалансировки по условию определяется максимальным абсолютным или относительным отклонением.
1. Абсолютное отклонение
Измеряет простую разницу (в процентных пунктах) между текущим и целевым распределением.
Формула:
Пример:
Цель: 40% облигаций
Текущий вес: 45% облигаций
Абсолютное отклонение: +5% (45% - 40%)
Когда использовать?
✔ Лучше всего подходит для ценных бумаг с большим весом (например, 60/40 акции/облигации)
2. Относительное отклонение
Измеряет отклонение в процентах от целевого веса.
Формула:
Пример:
Цель: 10% золота
Текущий вес: 12% золота
Относительное отклонение: +20% ( 12%-10% / 10% )
Когда использовать?
✔ Лучше подходит для небольших долей (например, изменение на 2% при целевом весе 10% = 20% относительного изменения)
Ключевые различия
Сценарий |
Абсолютное отклонение |
Относительное отклонение |
---|---|---|
Цель 50%, Текущий 55% |
+5% |
+10% |
Цель 10%, Текущий 12% |
+2% |
+20% |
Без ребалансировки ("Купи и держи")
Если инвестиционная стратегия не включает ребалансировку, веса активов в портфеле могут произвольно отклоняться от исходных целевых значений.
Почему стоит использовать Okama?
Okama — это библиотека с открытым исходным кодом для анализа инвестиционных портфелей со следующими возможностями
Анализ ценных бумаг и портфелей
Прогнозирование с помощью Монте-Карло
Использование различных видов распределений (нормальное, логнормальное, Стьюдента и т.п.)
Оптимизация и построение Границы эффектвности
Визуализации
Все подходы соответствуют рекомендациям CFA
Бесплатная база исторических данных бирж мира, валют, макростатистики
Подробное описание библиотеки Okama можно найти в предыдущей статье.
Перейдем к практике...
Настройка
Сначала установите Okama через pip:
pip install okama
Импорт пакетов:
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
import okama as ok
Настройка стратегии ребалансировки
Класс Rebalance
используется для настройки стратегий ребалансировки.
Базовая стратегия ребалансировки портфеля на основе календаря (выполняемая ежегодно) может быть настроена следующим образом:
# настройка стратегии ребалансировки на основе календаря с периодом 1 год
reb_calendar = ok.Rebalance(period="year")
Okama работает с ежемесячными рядами данных. Если период ребалансировки равен месяцу, мы имеем классическую ситуацию Марковица с постоянно ребалансируемым портфелем, в котором веса не отклоняются от целевых значений.
reb_calendar_month = ok.Rebalance(
period="month",
abs_deviation=None,
rel_deviation=None
)
То же самое для стратегии ребалансировки по условию с абсолютным отклонением 5%:
reb_bands_abs = ok.Rebalance(abs_deviation=0.05)
Стратегия с относительным отклонением 10%:
reb_bands_rel = ok.Rebalance(rel_deviation=0.10)
Все стратегии ребалансировки можно комбинировать:
reb = ok.Rebalance(period="half-year", abs_deviation=0.15, rel_deviation=0.10)
reb
Вывод:
period half-year
abs_deviation 0.15000
rel_deviation 0.10000
dtype: object
В гибридных стратегиях применяется календарный принцип (например, раз в год). Но ребалансировка запускается только в том случае, если отклонение в весе одного из активов превышает установленное условие.
Чтобы настроить стратегию без ребалансировки:
no_reb = ok.Rebalance(period="none")
no_reb
Вывод:
period none
abs_deviation None
rel_deviation None
dtype: object
Стратегии ребалансировки в Portfolio
Все стратегии ребалансировки доступны в классе Portfolio
okama и могут быть использованы для бэктестинга или прогнозирования. В okama все ребалансировки происходят в конце периода (последний день месяца, последний месяц года и т. д.).
Портфель 60/40
Как выглядит "идеальный портфель"? С точки зрения контроля рисков, в "идеальном" портфеле пропорции активов должны быть постоянными.
Давайте создадим простой портфель 60/40 с популярными облигациями и ETF-фондами акций и ежемесячной ребалансировкой (постоянные веса).
target_weights = [0.60, 0.40]
pf2 = ok.Portfolio(
["AGG.US", "SPY.US"],
weights=target_weights,
last_date="2025-06",
ccy="USD",
rebalancing_strategy=reb_calendar_month, # веса постоянны
inflation=False,)
pf2
Вывод:
symbol portfolio_8100.PF
assets [AGG.US, SPY.US]
weights [0.6, 0.4]
rebalancing_period month
rebalancing_abs_deviation None
rebalancing_rel_deviation None
currency USD
inflation None
first_date 2003-10
last_date 2025-06
period_length 21 years, 9 months
dtype: object
Свойство Portfolio.weights_ts
показывает, как изменялись веса портфеля в прошлом. Для этого набора ценных бумаг доступны исторические данные с глубиной 21 год.
weights_no_rebalancing = pf2.weights_ts
weights_no_rebalancing.plot();

Так выглядят веса активов в классической Современной теории портфеля (Modern Portfolio Theory) Марковица. Однако в реальной жизни держать веса в виде константы невозможно.
Противоположный случай — когда веса меняются без ограничений (портфель без ребалансировки).
# изменить стратегию ребалансировки
pf2.rebalancing_strategy = no_reb
# построить график весов
pf2.weights_ts.plot();

За несколько лет консервативный портфель стал агрессивным по мере роста доли акций.
abs(pf2.weights_ts - target_weights)["SPY.US"].max()
Вывод:
np.float64(0.36386072210732423)
Веса отклоняются от первоначального распределения на 36%. Такое вряд ли понравится кому-то из инвесторов. Попробуем это "вылечить" с помощью стратегий ребалансировки.
Для начала переключим стратегию ребалансировки в существующем портфеле на календарную ребалансировку с частотой 1 год.
pf2.rebalancing_strategy = reb_calendar
pf2
Вывод:
symbol portfolio_8100.PF
assets [AGG.US, SPY.US]
weights [0.6, 0.4]
rebalancing_period year
rebalancing_abs_deviation None
rebalancing_rel_deviation None
currency USD
inflation None
first_date 2003-10
last_date 2025-06
period_length 21 years, 9 months
dtype: object
Свойство Portfolio.rebalancing_events
показывает события ребалансировки на исторических данных:
ev = pf2.rebalancing_events
ev
Вывод:
2003-12 calendar
2004-12 calendar
2005-12 calendar
2006-12 calendar
2007-12 calendar
2008-12 calendar
2009-12 calendar
2010-12 calendar
2011-12 calendar
2012-12 calendar
2013-12 calendar
2014-12 calendar
2015-12 calendar
2016-12 calendar
2017-12 calendar
2018-12 calendar
2019-12 calendar
2020-12 calendar
2021-12 calendar
2022-12 calendar
2023-12 calendar
2024-12 calendar
Freq: M, dtype: object
При тестировании было 22 ребалансировки:
ev.shape
Вывод:
(22,)
Мы можем построить график всех событий, чтобы увидеть, когда происходила ребалансировка:
fig = plt.figure(figsize=(12, 6))
ax = plt.gca()
pf2.weights_ts.plot(ax=ax)
ax.vlines(x=ev[ev == "calendar"].index, ymin=0, ymax=1, colors="blue", ls="--", lw=1, label="События ребалансировки")
ax.set_ylim([0, 1])
ax.legend();

Давайте посмотрим на 5 самых больших отклонений весов:
abs(pf2.weights_ts - target_weights)["SPY.US"].nlargest(n=5)
Вывод:
2008-12 0.11913
2008-11 0.10794
2008-10 0.08644
2013-12 0.07366
2021-12 0.06630
Freq: M, Name: SPY.US, dtype: float64
Максимальное отклонение веса активов произошло в 2008 году во время финансового кризиса и достигло почти 12%. Это было значительно ниже, чем в сценарии без ребалансировки.
Давайте посмотрим, что произойдет, если мы используем вместо календарной ребалансировки стратегию с абсолютным отклонением 5%:
pf2.rebalancing_strategy = reb_bands_abs
pf2
Вывод:
symbol portfolio_8100.PF
assets [AGG.US, SPY.US]
weights [0.6, 0.4]
rebalancing_period none
rebalancing_abs_deviation 0.05000
rebalancing_rel_deviation None
currency USD
inflation None
first_date 2003-10
last_date 2025-06
period_length 21 years, 9 months
dtype: object
Теперь количество событий ребалансировки составляет 13.
ev = pf2.rebalancing_events
ev
Вывод:
2005-11 abs
2008-10 abs
2009-01 abs
2009-09 abs
2011-04 abs
2011-09 abs
2012-03 abs
2013-10 abs
2016-11 abs
2018-01 abs
2021-02 abs
2021-12 abs
2024-03 abs
Freq: M, dtype: object
ev.shape
Вывод:
(13,)
За исторический период произошло 13 ребалансировок — меньше, чем 22, требуемых при календарной стратегии. В реальной финансовой практике каждая ребалансировка влечет за собой расходы (брокерские комиссии, налоги). Снижения количества ребалансировок может дать существенную экономию...
fig = plt.figure(figsize=(12, 6))
ax = plt.gca()
pf2.weights_ts.plot(ax=ax)
ax.vlines(x=ev[ev == "abs"].index, ymin=0, ymax=1, colors="blue", ls="--", lw=1, label="Rebalancing events")
ax.set_ylim([0, 1])
ax.legend();

abs(pf2.weights_ts - target_weights)["SPY.US"].nlargest(n=5)
Вывод:
2008-10 0.06851
2018-01 0.06004
2021-12 0.05638
2013-10 0.05638
2024-03 0.05448
Freq: M, Name: SPY.US, dtype: float64
Отклонения весов теперь под контролем. Максимальное отклонение снизилось до 6,9% — почти половины от того, что произошло при календарной ребалансировке.
Интересно, что отклонение превышает 5-процентный лимит стратегии. Это происходит потому, что okama проверяет условия ребалансировки в конце месяца (используя месячные данные). В течение одного месяца веса активов могут отклоняться за пределы установленных стратегией лимитов.
Портфель из 3 активов с небольшим распределением
Мы можем протестировать ту же стратегию ребалансировки с абсолютными пределами отклонения веса в портфеле из 3 активов, где один актив (золото) имеет всего 5% распределения.
# установить целевые веса для активов
target_weights3 = [0.60, 0.35, 0.05]
# установить стратегию ребалансировки
rs3 = ok.Rebalance(
period="none",
abs_deviation=0.10,
rel_deviation=None
)
# создать портфель с 3 активами
pf3 = ok.Portfolio(
["SP500TR.INDX", "VBMFX.US", "GC.COMM"],
weights=target_weights3,
ccy="USD",
rebalancing_strategy=rs3,
inflation=False,)
pf3
Вывод:
symbol portfolio_1335.PF
assets [SP500TR.INDX, VBMFX.US, GC.COMM]
weights [0.6, 0.35, 0.05]
rebalancing_period none
rebalancing_abs_deviation 0.10000
rebalancing_rel_deviation None
currency USD
inflation None
first_date 1988-02
last_date 2025-06
period_length 37 years, 5 months
dtype: object
Во время бэктестинга условие ребалансировки срабатывало 8 раз.
ev3 = pf3.rebalancing_events
ev3
Вывод:
1995-07 abs
1997-07 abs
2002-09 abs
2007-01 abs
2008-10 abs
2013-12 abs
2018-08 abs
2023-06 abs
Freq: M, dtype: object
Посмотрим эти события ребалансировки на графике.
fig = plt.figure(figsize=(12, 6))
ax = plt.gca()
pf3.weights_ts.plot(ax=ax)
ax.vlines(x=ev3.index, ymin=0, ymax=1, colors="blue", ls="--", lw=1, label="Rebalancing by abs")
ax.set_ylim([0, 1])
ax.legend();

Были ситуации, когда отклонение для золота было слишком большим. Максимальное отклонение составляет 4,3%, что почти вдвое (!) превышает размер целевого веса для золота.
abs(pf3.weights_ts - target_weights3).max()
Вывод:
Symbols
SP500TR.INDX 0.10438
VBMFX.US 0.10640
GC.COMM 0.04257
dtype: float64
В этом случае следует добавить условие относительного отклонения, чтобы лучше контролировать распределение для золота.
pf3.rebalancing_strategy = ok.Rebalance(
period="none",
abs_deviation=0.10,
rel_deviation=0.30 # относительное отклонение 30%
)
Количество ребалансировок увеличилось до 14.
ev3 = pf3.rebalancing_events
ev3.shape
Вывод:
(14,)
ev3
Вывод:
1989-05 rel
1992-04 rel
1996-06 rel
1997-07 rel
1999-01 rel
2002-07 abs
2006-01 rel
2008-01 rel
2008-12 abs
2011-07 rel
2013-06 rel
2017-10 abs
2021-08 abs
2025-03 rel
Freq: M, dtype: object
Теперь у нас есть 10 ребалансировок по относительному условию и только 4 по абсолютному.
Новые события ребалансировки отмечены на графике зеленым цветом.
fig = plt.figure(figsize=(12, 6))
ax = plt.gca()
pf3.weights_ts.plot(ax=ax)
ax.vlines(x=ev3[ev3 == "abs"].index, ymin=0, ymax=1, colors="blue", ls="--", lw=1, label="Rebalancing by abs")
ax.vlines(x=ev3[ev3 == "rel"].index, ymin=0, ymax=1, colors="green", ls="--", lw=1, label="Rebalancing by rel")
ax.set_ylim([0, 1])
ax.legend();

Теперь максимальное отклонение золота составляет 2,2%. И это выглядит лучше.
abs(pf3.weights_ts - target_weights3).max()
Symbols
SP500TR.INDX 0.11944
VBMFX.US 0.11290
GC.COMM 0.02217
dtype: float64
Мы можем протестировать альтернативный подход: реализовать календарную ребалансировку, которая срабатывает только при превышении пороговых значений абсолютного или относительного отклонения. Этот гибридный метод широко используется на практике, устраняя необходимость постоянного мониторинга весов. Вместо этого инвесторы просто пересматривают свое распределение раз в год (или раз в полгода).
pf3.rebalancing_strategy = ok.Rebalance(
period="year",
abs_deviation=0.10,
rel_deviation=0.20
)
Количество событий ребалансировки увеличилось на 2.
ev3 = pf3.rebalancing_events
ev3.shape
Вывод:
(16,)
В таблице событий мы видим, что ребалансировки происходят только в декабре.
ev3
Вывод:
1989-12 rel
1991-12 rel
1995-12 rel
1997-12 rel
1999-12 rel
2001-12 rel
2002-12 rel
2006-12 rel
2007-12 rel
2008-12 abs
2011-12 rel
2013-12 abs
2015-12 rel
2019-12 rel
2021-12 rel
2024-12 rel
Freq: M, dtype: object
Большинство из них запускаются относительным условием. Если вы чувствуете себя уверенно при больших отклонениях, количество событий можно уменьшить, настроив параметр относительного отклонения rel_deviation
.
ev3[ev3 == "rel"].count()
Вывод:
np.int64(14)
ev3[ev3 == "abs"].count()
Вывод:
np.int64(2)
fig = plt.figure(figsize=(12, 6))
ax = plt.gca()
pf3.weights_ts.plot(ax=ax)
ax.vlines(x=ev3[ev3 == "abs"].index, ymin=0, ymax=1, colors="blue", ls="--", lw=1, label="Rebalancing by abs")
ax.vlines(x=ev3[ev3 == "rel"].index, ymin=0, ymax=1, colors="green", ls="--", lw=1, label="Rebalancing by rel")
ax.set_ylim([0, 1])
ax.legend();

На графике мы видим, что было несколько длительных периодов без ребалансировки.
И максимальное отклонение по-прежнему находится под контролем.
abs(pf3.weights_ts - target_weights3).max()
Вывод:
Symbols
SP500TR.INDX 0.12670
VBMFX.US 0.11787
GC.COMM 0.02512
dtype: float64
Для пассивного подхода к инвестированию, когда необходимость ребалансировки портфеля проверяется "руками" без автоматически настроенных инструментов, гибридный подход с длительными интервалами (год или полгода) удобен, поскольку устраняет необходимость постоянного мониторинга отклонений весов. С другой стороны, условия по-прежнему не позволяют портфелю слишком сильно отклоняться от намеченной стратегии.
В то же время, чем больше допустимые отклонения веса, тем больше потенциальный бонус от ребалансировки в виде дополнительной доходности.
Бонус за ребалансировку: избыточная доходность
Согласно исследованию Уильяма Бернстайна, ребалансировка позволяет не только контролировать риск, но и может принести дополнительную доходность. Этот эффект возникает, если портфель содержит активы с низкой корреляцией. Библиотека okama позволяет изучить влияние стратегий ребалансировки на эту избыточную доходность и оптимизировать распределение активов. Если вам интересна эта тема, дайте знать в комментариях... Покажу несколько примеров в следующей публикации.
Maksatiha
Мозгари, объясните теперь куда мне просто деньги положить, а потом снять с супер-пупер избыточной доходностью? Желательно с карты на карту.
Chilango Автор
А с чего вы взяли, что этот вопрос к автору именно этой статьи? :)
lczero
--