Практические аспекты ранней пенсии FIRE movement в России +4


Ранний выход на пенсию является горячей темой в последнее время. Отчасти это связано с движением FIRE: «финансовая независимость и ранняя пенсия» — зарабатывать больше, тратить меньше и рано сбросить оковы работы.



Казалось бы причем здесь FIRE и habr.com? А при том, что невозможно добиться этой ранней пенсии НЕ имея и НЕ учитывая собственных диверсифицированных активов. А как показала многолетняя практика адекватных инструментов для учета акций, облигаций, недвижимости, монет и валют вкупе со страновой диверсификацией в одном окне практически нет. В небольшой предыстории рассказывал, что если у Вас есть, что учитывать, то сам учёт может занимать неоправданно много времени. Но тут на помощь приходит парсинг сайтов и это выводит управленческий учет собственного портфеля на новый уровень и, конечно же, приближает раннюю пенсию.


Важное замечание: эта статья для начинающего раннего пенсионера и новичка-парсера :). Мой опыт подразумевает использование данных с сайтов только для личных целей. Коснусь использования Microsoft Excel и Google Таблиц и совсем чуть-чуть KMyMoney. Начну в том порядке, в котором я сам столкнулся со всеми продуктами:


Часть 1. KMyMoney


Добавление нового актива в KMyMoney
Добавление нового актива в KMyMoney


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


Настройка парсера в KMyMoney
Настройка парсера в KMyMoney


Также любопытно, что из версии в версию KMyMoney (я пользовался программой несколько лет) в настройках парсера кочуют какие-то неработающие ссылки, которыми практически невозможно воспользоваться и нет какого-то внятного описания компонента.


Указание использовать шаблон парсинга для конкретной бумаги
Указание использовать шаблон парсинга для конкретной бумаги


Предлагаю для примера какую-нибудь облигацию федерального займа (ОФЗ) — это рублёвые облигации, выпускаемые Министерством финансов Российской Федерации. Данные по этим облигациям можно посмотреть на множестве сайтов или через API Московской Биржи. Возьму ОФЗ-ПК 29012 (SU29012RMFS0), а цену буду смотреть на сайте одного из брокеров. Идентификатором станет не код ценной бумаги с биржи, а внутренний идентификатор с сайта. Так выглядит окно с уже скаченной котировкой предыдущего дня:


Результат работы парсера для бумаги
Результат работы парсера для бумаги


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


Конечно, KMyMoney позволяет иметь автоматические обновление курсов любых активов и это наверное единственная программа которая позволяет это делать, но как и у всех подобных учетных программ у неё есть определенные недостатки. А именно: раздражающий “бухгалтерский поиск” не сходящихся копеек (мне нужен обзорный инструмент) и огромное количество кликанья мышкой (в два клика ничего не сделать с инвестицией). Но эта программа наверное лучшее, если Вы хотите иметь учет личных финансов и инвестиций в одном окне и при этом иметь автоматические обновление курсов любых активов.


Часть 2. Microsoft Excel


Встроенных функций для парсинга в Excel нет и поэтому пользовался VBA, на основе найденных в интернете примеров (сам не особо разбирался в то время, чтобы написать с нуля) — код вероятно не самый лаконичный, но свои функции выполнял:


Редактор кода в Microsoft Excel
Редактор кода в Microsoft Excel


VBA код для парсинга цен облигаций федерального займа.
Public Function FinamPriceBondsCorporate(Optional ByVal ISIN) As Double
    '//объявляем переменные
    Dim sURI As String
    Dim oHttp As Object
    Dim htmlcode, outstr As String
    Dim Num As Double

    '//обращаемся к сайту
    sURI = "https://bonds.finam.ru/issue/" & ISIN & "/default.asp?resultsType=5"
    On Error Resume Next
    Set oHttp = CreateObject("MSXML2.XMLHTTP")
    If Err.Number <> 0 Then
        Set oHttp = CreateObject("MSXML.XMLHTTPRequest")
    End If
    On Error GoTo 0
    If oHttp Is Nothing Then
        Exit Function
    End If
    oHttp.Open "GET", sURI, False
    oHttp.Send
    htmlcode = oHttp.responseText

    '//попробовать regex!
    outstr = Mid(htmlcode, InStr(1, htmlcode, "Сlose:</td>") + 57, 6) '//ищем значение в тексте
    Set oHttp = Nothing

    '//удаляем куски html разметки, если нужно
    outstr = Replace(outstr, "&", "")
    outstr = Replace(outstr, "n", "")
    outstr = Replace(outstr, "b", "")
    outstr = Replace(outstr, "s", "")
    outstr = Replace(outstr, "p", "")
    outstr = Replace(outstr, ";", "")
    outstr = Replace(outstr, "-", "0")
    Num = CDbl(outstr) '//число в текст

    If Num = 0 Then  '//если нет цены закрытия берем другую
    outstr = Mid(htmlcode, InStr(1, htmlcode, "Bid:</td>") + 54, 6) '//ищем значение в тексте
    outstr = Replace(outstr, "&", "")
    outstr = Replace(outstr, "n", "")
    outstr = Replace(outstr, "b", "")
    outstr = Replace(outstr, "s", "")
    outstr = Replace(outstr, "p", "")
    outstr = Replace(outstr, ";", "")
    outstr = Replace(outstr, "-", "0")
    Num = CDbl(outstr) '//число в текст
    End If

    FinamPriceBondsCorporate = Num

    Exit Function
ErrorHandler: 'Обработчик ошибок
    FinamPriceBondsCorporate = 0
    Err.Clear
End Function

Для того, чтобы воспользоваться кодом, в самом Excel надо вызвать эту, только что написанную функцию FinamPriceBondsCorporate:


Excel и использование дополнительной функции парсинга
Excel и использование дополнительной функции парсинга


Таблица в Excel (и учётный шаблон на её основе) оставалась удобным локальным вариантом, если хотел открыть её и что-то в ней посмотреть. Но котировки активов не могли обновляться без участия человека и уж тем более эта таблица не могла утром в понедельник послать отчет и еще создать мероприятия в календаре о выплатах на неделю вперед. В общем, пришлось двинуться дальше.


Часть 3. Google Таблицы


Котировки через IMPORTXML


Google Таблицы как оказалось представляют гораздо более широкий диапазон возможностей для парсинга. И первое с чем столкнулся — это была функция IMPORTXML, которая позволяла взять почти любую строку из веб-страницы. А для использования надо только было научиться писать xpath запрос. С ее помощью можно собирать нужные котировки и другую информацию без скриптов и интеграции с API.



Мой шаблон отчетной таблицы, которому дал название SilverFir: Investment Report


Примерно за полгода изысканий в 2018 году родилась окончательная версия моего шаблона, который стал отправной точкой для взрослого учёта диверсифицированных активов “в одном окне”.


Распределение активов в отчетной таблице SilverFir: Investment Report
Распределение активов в отчетной таблице SilverFir: Investment Report


К сожалению, когда в Таблице использовалось множество IMPORTXML приходилось слишком долго ждать загрузки всех результатов, а также обнаружилась невозможность использования данных полученных через IMPORTXML по расписанию в гугл скриптах (потому что они могли просто не успеть подгрузиться к моменту их использования).


Котировки через Google Apps Script


Когда стало понятно, что при большом количестве строк и запрашиваемых данных пользоваться отчетной Таблицей на IMPORTXML становится неудобно — пришлось смотреть дальше.


Google Apps Script приходит на помощь в FIRE movement
Google Apps Script приходит на помощь в FIRE movement


Поскольку всегда считал, что FIRE movement — это свобода заниматься тем что нравится, а не бухгалтерский поиск копеек и уж конечно не копипаста котировок с разных сайтов. Структура и компоновка отчетной таблицы получилась очень удачная (это отмечали и другие люди), а вот метод получения котировок пришлось опять менять.


На гугл скриптах парсинг, на мой взгляд, выглядит лаконичнее, чем на VBA.


Google Apps Script для парсинга имени и котировки Pitney-Bowes-Anleihe (3,875% до 01.10.2021) с ISIN US724479AK60 и подобных.
function GetFinanzenNet(ISIN, id) {
    var url = 'https://www.finanzen.net/suchergebnis.asp?_search=' + ISIN;
    try {
        var html = UrlFetchApp.fetch(url).getContentText();
    } catch (error) {
        Logger.log("GetFinanzenNet. Symbol = " + ISIN + ".\nОшибка чтения данных. URL: " + url + ". HTML:\n" + html);
        return ""
    }
    if (id == "Price") { //цена акции - Quote/Price
        var searchstring = 'text-sm-right text-nowrap">';
        var index = html.search(searchstring);
        if (index >= 0) {
            var pos = index + searchstring.length
            var rate = html.substring(pos, pos + 50);
            rate = rate.split('<')[0];
            rate = rate.replace(/\,/g, ".");
            rate = rate.replace(/\%/g, "");
            rate = +rate;
            Logger.log("GetFinanzenNet. ID = " + id + ".\nPrice = " + rate + ". URL: " + url);
            return rate
        }
    }
    if (id == "Name") { //имя
        var searchstring5 = '<title>';
        var index5 = html.search(searchstring5);
        if (index5 >= 0) {
            var pos5 = index5 + searchstring5.length
            var rate5 = html.substring(pos5, pos5 + 100);
            try {
                rate5 = rate5.split(' | ')[1];
                rate5 = rate5.split(' | ')[0];
                var res = rate5.substring(0, 1); //первый символ
                if (res == "(") {
                    rate5 = html.substring(pos5, pos5 + 100);
                    rate5 = rate5.split(' | ')[0];
                    rate5 = rate5.substring(0, 18);
                }
            } catch (error) {
                return "";
            }
            Logger.log("GetFinanzenNet. Найден Name для Symbol " + ISIN + ".\nName = " + rate5 + ". URL: " + url);
            return rate5
        }
    }
    Logger.log("GetFinanzenNet. Symbol = " + ISIN + ".\nОшибка чтения данных. URL: " + url);
    return ""
}

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


Экосистема Google


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


Вот примеры функций, которые можно было реализовать на скриптах.
  • еженедельные отчёты на почту;


Пример фрагмента отчета на почте


  • события с выплатами в календаре;


Пример тестового события в Google Календарь


  • задачи посмотреть, что не так с теми или иными бумагами;


Пример Google Задач


  • ежемесячная презентация с итогами месяца;


Пример Google Презентации с изменениями на скриптах


  • Asset Allocation на основе данных с множества сайтов;


Промежуточные шаги для расчета диверсификации: типы и страны


  • дополнительные диаграммы и графики, которых нет в Таблицах;


Пример сгенерированного Word Trees Charts — очень наглядно раскидать собственный портфель (на скриншоте НЕ он) по валютам и классам активов.


  • напоминания об офертах и выплатах в телеграм;


Бот с напоминаниями


  • импорт данных от брокеров (у Российских брокеров как-то мне показалось сложно с этим, поскольку у каждого брокера свое видение отчёта, причем в большинстве случаев для инвестора недружественное).

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


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


Итог


Еще раз возвращаясь к практическим аспектам ранней пенсии FIRE movement в России — учёт имеет к ранней пенсии самое прямое отношение и именно грамотно поставленный учет позволяет держать руку на пульсе, в то же самое время не скатываясь до частого просмотра котировок и текущих цен.


Автор: Михаил Шардин,
25 ноября 2019 г.




К сожалению, не доступен сервер mySQL