Методология расчета облигаций с плавающей ставкой и номиналом

Методика расчета облигаций с плавающей ставкой и номиналом в системе ТрэкРекордс

Общее описание

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

Пользователь может использовать собственный сценарий об изменении ставок, нулевой сценарий или вмененный сценарий (по умолчанию)

Нулевой сценарий предполагает, что ставки купонов/номинала остаются на уровне, на котором они были на дату расчета.

Вмененный сценарий предполагает расчет вмененных ставок купонов/номинала на основе информации о торгах линкеров и флоатеров ГЦБ РФ и отдельных корпоративных облигаций. Таким образом вмененный сценарий оценивает ожидания рынка относительно ставок RUONIA, RU_CPI и RU_RATE на дату расчета.

При расчете вмененных показателей RUONIA и RU_CPI ТрэкРекордс использует подход, разработанный Банком России при стресс-тестировании пенсионных фондов и оценке флоатеров и линкеров. В новой редакции Указания Банка России №4060-У предполагается нахождение вмененных ставок инфляции и ставок RUONIA на основе параметров ОФЗ и их цен на рынке. Проект Указания можно почитать по этой ссылке {.is-info}

Вмененный сценарий

Для облигаций, у которых размер купона или номинала зависит от ставок RUONIA, RU_CPI и RU_RATE, в ТрэкРекордс по умолчанию используется вмененный сценарий расчета прогнозных базовых индикаторов купона/номинала.

ТрэкРекордс рассчитывает вмененные показатели на основании цен и характеристик ОФЗ и отдельных корпоративных облигаций, у которых есть ликвидность и история торгов. Список облигаций, являющийся базой для расчета вмененных показателей, обновляется раз в квартал. Дополнительном фильтром для выбора базовых облигаций при расчете вмененной ставки RU_RATE является агрегированный рейтинг облигации/эмитента не ниже ruAAA.

Пример расчета вмененных ставок и база расчета на 2024-03-19:

ДатаСтавкаСрок, днейСрок, летВмененная
ставка, %
ISIN опорной
облигации
Опорная облигацияЦена с НКД
2024-03-19RUONIA3160,8715,2RU000A0JV4L2ОФЗ-29006-ПК1029,72
2024-03-19RUONIA7362,0213,4RU000A101N52ОФЗ-29014-ПК1033,72
2024-03-19RUONIA10092,7613,06RU000A1025B5ОФЗ-29016-ПК1033,94
2024-03-19RUONIA10792,9613,27RU000A0JV4M0ОФЗ-29007-ПК1048,11
2024-03-19RUONIA12823,5112,81RU000A102BV4ОФЗ-29020-ПК1032,84
2024-03-19RUONIA16744,5912,54RU000A1025A7ОФЗ-29015-ПК1020,63
2024-03-19RUONIA19475,3312,35RU000A102A49ОФЗ-29019-ПК1016,21
2024-03-19RUONIA20245,5512,16RU000A0JV4P3ОФЗ-29008-ПК1080,42
2024-03-19RUONIA23746,512,37RU000A101KT1ОФЗ-29013-ПК1026,99
2024-03-19RUONIA24446,712,22RU000A105B11ОФЗ-29021-ПК996,54
2024-03-19RUONIA28087,6912,28RU000A102A31ОФЗ-29018-ПК997,45
2024-03-19RUONIA29698,1311,98RU000A0JV4N8ОФЗ-29009-ПК1079,47
2024-03-19RUONIA30818,4412,31RU000A1028D5ОФЗ-29017-ПК997,48
2024-03-19RUONIA34109,3412,37RU000A105G16ОФЗ-29022-ПК1010,41
2024-03-19RUONIA380910,4412,28RU000A105L19ОФЗ-29023-ПК989,99
2024-03-19RUONIA391410,7211,93RU000A0JV4Q1ОФЗ-29010-ПК1076,8
2024-03-19RUONIA404711,0912,28RU000A1066D5ОФЗ-29024-ПК999,34
2024-03-19RUONIA489413,4112,38RU000A106Z61ОФЗ-29025-ПК988,16
2024-03-19RU_CPI14153,886,96RU000A0ZYZ26ОФЗ-52002-ИН1310,39
2024-03-19RU_CPI23116,337,12RU000A102069ОФЗ-52003-ИН1138,65
2024-03-19RU_CPI292087,33RU000A103MX5ОФЗ-52004-ИН1051,27
2024-03-19RU_RATE8962,4512,72RU000A106TM6Банк ВТБ-Б-1-3431005,79
2024-03-19RU_RATE9422,5812,09RU000A1075S4ИКС 5 Финанс-003P-02-боб1017,93
2024-03-19RU_RATE9872,712,65RU000A107B35Банк ВТБ-Б-1-3461006,59
2024-03-19RU_RATE9982,7311,77RU000A107HG1Газпром нефть-003P-08R1018,83
2024-03-19RU_RATE10602,911,41RU000A107UW1Газпром нефть-003P-10R1011,48
2024-03-19RU_RATE11553,1611,63RU000A107AG6Россети Центр-001P-031006,85
2024-03-19RU_RATE13483,6911,32RU000A107EC7Россети Ленэнерго-001P-011003,44
2024-03-19RU_RATE14874,0711,36RU000A106565Газпром нефть-003P-06R1035,04
2024-03-19RU_RATE16604,5511,35RU000A106ZU6РусГидро-БО-П121037,02
2024-03-19RU_RATE20925,7311,19RU000A107CG2Россети-001Р-11R1004,74
2024-03-19RU_RATE35169,6310,42RU000A1077V4Росэксимбанк-БО-002Р-041020,97

Алгоритм расчета значений вмененной инфляции RU_CPI

  1. Получаем параметры облигации (ставка купона, остаточный номинал, срок до погашения, базовая ставка купона/номинала, премия к базовой ставке)
  2. Получаем календарь денежных потоков по облигации
  3. Рассчитываем грязную цену облигации на дату расчета в основном режиме торгов:
Грязная цена облигации = Первая ненулевая(close, waprice, bid) / 100 * Непогашенный номинал + нкд.

close - цена закрытия %
waprice - средневзвешенная цена %
bid - цена покупки %
Непогашенный номинал - Номинальная стоимость на дату расчетов
нкд - накопленный купонный доход на дату расчета
  1. Получаем кривую доходности ОФЗ на дату расчета для сроков (3m, 6m,9m, 1Y, 2Y, 3Y, 5Y, 7Y, 10Y, 15Y, 20Y, 30Y)

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

  1. Расчет вмененной ставки при помощи функции get_implied_inflation

Функция get_implied_inflation предназначена для расчета вмененной инфляции на указанную дату (stated_at) на основе данных о “грязной” цене ценной бумаги (dirty_price), номинале (nom), датах купонных выплат (dc), кривой ОФЗ (daily_rates_on_stated_at) и ставке купона (coupon_rate).

Итеративный процесс поиска инфляции

Функция использует метод бисекции для нахождения значения инфляции, при котором сумма дисконтированных потоков близка к “грязной” цене с заданной точностью. Для каждого шага итерации рассчитывается величина PR (дисконтированная сумма денежных потоков) с учетом текущего значения предполагаемой инфляции, а затем корректируется диапазон поиска в зависимости от того, больше или меньше PR фактической “грязной” цены.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

#функция для расчета вмененной инфляции на дату stated_at
def implied_inflation(stated_at, dirty_price, nom, dc, daily_rates_on_stated_at, coupon_rate):
    N = len(dc) # Количество купонов
    Il = -0.1 # Нижняя граница для инфляции
    Ir = 0.6 # Верхняя граница для инфляции
    Inf = Il # Начальное значение для предполагаемой инфляции
    DEL = 1 # Начальное значение для разницы между "грязной" ценой и ценой, рассчитанной с использованием предполагаемой инфляции
    br = 1 # Счетчик для ограничения количества итераций
    coupon_rate = float(coupon_rate) # Преобразование ставки купона в число с плавающей точкой
    stated_at = pd.to_datetime(stated_at) # Преобразование даты stated_at в объект datetime
    dc = pd.to_datetime(dc) # Преобразование дат купонов в объекты datetime
    dc = dc.reset_index(drop=True) # Сброс индексов для корректного использования в цикле

    while abs(DEL) > 0.00001 and br < 1001:
        if DEL > 0:
            Il = Inf
        else:
            Ir = Inf
        Inf = (Il + Ir) / 2
        PR = 0
        for i in range(1, N):
            d = dc[i]
            if d > stated_at:
                cnp_dur = (d - stated_at).days
                dKBD = (1 + get_kbdrate_from_data(daily_rates_on_stated_at,cnp_dur,'RUB')/100) ** (cnp_dur / 365)
                dInf = (1 + Inf) ** (cnp_dur / 365)
                PR += nom * dInf * coupon_rate * (d - dc[i - 1]).days / 365 / dKBD
                if i == N - 1: 
                    PR += nom * dInf / dKBD
        DEL = dirty_price - PR
        br += 1
        
    if br < 1000:
        return Inf
    else:
        return "Error"

Алгоритм расчета значений вмененной инфляции RUONIA или RU_RATE

Алгоритм расчета вмененной ставки RUONIA или RU_RATE сводится к нахождению разницы между “грязной” ценой облигации и суммой дисконтированных премий к базовой ставке и известных купонов (гарантированных и уже известных платежей), деленной на дисконтированный номинал.

Для RUONIA в качестве ставки дисконтирования используется кривая ОФЗ, т.к. опорными облигациями являются ОФЗ-ПК Для RU_RATE(ключевая ставка) в качестве ставки дисконтирования используется используется кривая ОФЗ + gspread, рассчитанный по индексу МосБиржи RUCBTR3A3YNS, т.к. опорными облигациями являются корпоративные облигации с рейтингом не ниже ruAAA.

Пример функции расчета на Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#пишем функцию для расчета implied_rate для флоатера с премией к базовой ставке
def get_implied_yield(stated_at, dirtyprice, nominal, cashflows, daily_rates_on_stated_at, premium):
    """
    Calculates a specific financial metric (interpreted from the provided VBA function).
    
    :param stated_at: Дата расчета
    :param pv: Приведенная стоимость
    :param Nom: Номинал
    :param dc: Календарь денежных потоков
    :param daily_rates_on_stated_at: Кривая доходности ОФЗ на дату расчета
    :param pvb: Приведенная стоимость без купонов
    :param premium: Премия к базовой ставке
    :return: Значение implied_rate
    """
    PR1 = 0
    PR2 = 0
    pv = dirtyprice
    Nom = nominal
    stated_at = pd.to_datetime(stated_at).date()

    if len(cashflows) == 0:
        return 0
    
    #преобразуем календарь денежных потоков в словарь
    dc = cashflows.to_dict('records')
    N = len(dc)

    for i in range(N):
        #получаем дату купона и размер купона в деньгах
        d = dc[i]['coupon_date']
        cur_coupon = dc[i]['cf_coupon']

        if d > stated_at:
            #получаем дельту в днях между stated_at и датой купона
            delta_days = (d - stated_at).days
            #получаем коэффициент дисконирования КБД для дюрации купона
            dKBD = (1 + get_kbdrate_from_data(daily_rates_on_stated_at,delta_days,'RUB')/100) ** (delta_days / 365)
            
            #если купон известен то добавляем его к PR2
            if cur_coupon > 0:
                PR2 += cur_coupon / dKBD
            else:
                #если первый купон неизвестен, то предыдущая дата купона не может быть найдена значит она = stated_at
                if i > 0:
                    prev_d = dc[i - 1]['coupon_date']
                else:
                    prev_d = stated_at

                delta_prev_days = (d - prev_d).days
                PR1 += delta_prev_days / 365.0 / dKBD
                PR2 += Nom * premium * delta_prev_days / 365 / dKBD
            if i == N - 1:
                PR2 += Nom / dKBD

    return (pv - PR2) / (PR1 * Nom) 

Оценка вмененной ставки для разных сроков погашения

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

1
impl_rate_for_duration = (1+yield_low)^((dur_up - dur)/(dur_up - dur_low))*(1+yield_up)^((dur - dur_low)/(dur_up - dur_low)) - 1

где:

  • impl_rate_for_duration - вмененная ставка с учетом срока до погашения ценной бумаги

  • yield_up - значение вмененной ставки, расчитанной по облигации, которая имеет ближайший больший, чем у оцениваемой облигации, срок до погашения.

  • yield_low - значение вмененной ставки, расчитанной по облигации, которая имеет ближайший меньший, чем у оцениваемой облигации, срок до погашения.

  • dur_up - срок до погашения облигации, которая имеет ближайший больший, чем у оцениваемой облигации, срок до погашения.

  • dur_low - срок до погашения облигации, которая имеет ближайший меньший, чем у оцениваемой облигации, срок до погашения.

  • dur - срок до погашения оцениваемой облигации.

  • при отсутствии вмененной ставкисо срокос до погашения меньше срока до погашения оцениваемой облигации, значение показателя принимается равным значению фактической ставки на расчетную дату;

  • оферты по облигациям игнорируются при оценке dur_low и dur_up