Pull to refresh

Tinkoff инвестиции –> Prometheus –> Grafana

Reading time9 min
Views15K

Муки выбора

В последние год-два появилось много постов, как сделать таблички в Google Sheets/MS Excel для отображения актуальной информации о своем инвестиционном портфеле и т.п. Действительно хочется видеть, что там творится. Особенно актуально для продвинутых подписок/тарифных планов, когда требуется определенная сумма на счетах, и будет обидно, если из-за падения акций или курса доллара сумма снизится ниже пороговой…

Есть много инструментов для отслеживания инвестиционного портфеля. И сервисы, и таблицы… Мне хотелось иметь что-то с одной стороны легкое (не требуется учета купонов и т.п.), а с другой – чтобы легко настроить именно такой вид, такие параметры и срезы, как хочется именно мне.

Так что я попробовал несколько вариантов от электронных таблиц до записи в MySQL скриптом на python, и на текущий момент остановился на отображении моих инвестиционных счетов в Grafana.

MS Excel

Поскольку с ним и по работе сталкиваться приходится, всякие Pivot, графики строить иногда, это для меня самый привычный инструмент, и начал я именно с него.

К счастью, вовремя обнаружил, что на Android ни через приложение (даже при подписке Office Home, что на 5 членов семьи), ни через web, формулы FilterXML не работают.

Пробовал и другие клиенты, и всякие Libre Office, ничего не помогло. А я хотел универсальное решение, и со смартфона, и с планшета…

Google Sheet

Пришлось осваивать. И по сию пору держу там подборку интересных инструментов, вытаскиваю по названию ETF то, что можно. Но некоторые хотелки я реализовать не смог.

Это и «исторические данные» (хотелось смотреть графики изменений, пусть примерные, без свечей, конечно, но все-таки). И возможность сохранения (при экспорте в Excel работоспособность теряется, а я очень привык иметь файл на компьютере, доступный offline, и при этом периодически синхронизирующийся с OneDrive).

И конфиденциальность (в OneDrive я синкаю через GoodSync с шифрованием, т.е. в облаке хранится версия, зашифрованная моим локальным ключом, а в Google Sheet все, включая токен к Тинкофф Инвестициям, было бы в открытом виде).

Python + MS Excel

В рамках осваивания python написал скриптик, дергающий Инвестиции и сохраняющий все (в т.ч. периодически историю баланса аккаунта) в XSLX.

Разумеется, тут можно накрутить много чего. Но все-таки с построением графиков и т.п. были сложности. Частые апдейты через cron на отдельной машине – слишком файл разрастется. Да и на ноут его забирать… С ноута непосредственно скрипт запускать – не всегда в одно и то же время возможно, графики толком не построить…

Пробовал в MySQL писать – из Excel не смог удобно графики строить даже через MySQL Workbench. Можно, наверное, но понял, что усилия для этого потребуются неадекватные (как выбирать временной интервал для отображения - масштаб, смещение...)

Текущий финальный выбор

Поскольку в это же время я разбирался с популярными системами мониторинга, а именно с Prometheus и Grafana, стал делать на них дашборды для домашней лабы и других личных серверов, подумал: «А почему бы и финансовый дашборд не замутить?»

Погуглил, нашел пару проектов по экспорту из Тинькофф Инвестиций в Prometheus, решил, что раз другие так делают, это не совсем дурацкая идея, и стал делать.

Что получилось

Посмотрел пару репозиториев (https://github.com/maksim77/tinkoff_exporter и https://github.com/byumov/tinkoff_investing_exporter). Не совсем мне подошли. Поскольку написаны Go, которого я совсем не знаю, поправить их я не смог, поэтому стал писать свое на python. И воспользовался некоторыми идеями по организации дашборда на Grafana.

В итоге получилось https://github.com/Anrikigai/promTinkoff. Описание там есть, а здесь я больше сфокусируюсь на особенностях и проблемах. Может быть кому-то это поможет сделать что-то более информативное.

Prometheus

В контейнере запускаю скрипт, периодически опрашивающий API Tinkoff Инвестиции и презентующий в формате Prometheus. По умолчанию раз в 10 минут, этот же интервал указан и конфигурации Prometheus:

- job_name: 'tinkoff'
  scrape_interval: 600s
   static_configs:
     - targets: ['promTinkoff:8848']

Поскольку сам экспортер тоже в контейнере на той же машине, указывается просто его имя.

В конечном итоге данные представляются в виде

tcs_item{account="Tinkoff", balance_currency="EUR", currency="EUR", instance="promTinkoff:8848", job="tinkoff", name="Тинькофф Вечный портфель EUR", ticker="TEUR", type="Etf"}

tcs_item{account="Tinkoff", balance_currency="USD", currency="EUR", instance="promTinkoff:8848", job="tinkoff", name="Тинькофф Вечный портфель EUR", ticker="TEUR", type="Etf"}

tcs_item{account="Tinkoff", balance_currency="RUB", currency="EUR", instance="promTinkoff:8848", job="tinkoff", name="Тинькофф Вечный портфель EUR", ticker="TEUR", type="Etf"}

tcs_item{account="Tinkoff", balance_currency="RUB", currency="USD", instance="promTinkoff:8848", job="tinkoff", name="Pfizer", ticker="PFE", type="Stock"}

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

При этом currency получается от Tinkoff. Скажем, для "Тинькофф Вечный портфель EUR" currency=EUR, для Pfizer - currency=USD.

Аналогичный подход с дублированием в трех валютах используется для оценки прибыли/убытков в tcs_yield.

tcs_yield{account="Tinkoff", balance_currency="EUR", currency="USD", instance="promTinkoff:8848", job="tinkoff", name="Pfizer", ticker="PFE", type="Stock"} 90.99262579525735

tcs_yield{account="Tinkoff", balance_currency="USD", currency="USD", instance="promTinkoff:8848", job="tinkoff", name="Pfizer", ticker="PFE", type="Stock"} 107.5

tcs_yield{account="Tinkoff", balance_currency="RUB", currency="USD", instance="promTinkoff:8848", job="tinkoff", name="Pfizer", ticker="PFE", type="Stock"} 7866.3125

Также сохраняю курсы в tcs_rate.

И помимо вывода данных по каждому аккаунту, получаемого от Тинькофф Инвестиций (в данном случае Tinkoff, TinkoffIis), также добавляю автоматически посчитанные записи для фиктивного аккаунта _Total_

tcs_item{account="_Total_", balance_currency="RUB", currency="Multi", instance="promTinkoff:8848", job="tinkoff", name="_Total_", ticker="_Total_", type="_Total_"}

tcs_yield{account="_Total_", balance_currency="RUB", currency="Multi", instance="promTinkoff:8848", job="tinkoff", name="_Total_", ticker="_Total_", type="_Total_"}

Grafana

Суммы брокерского аккаунта скрою, буду показывать на примере недавного перенесенного в Тинькофф ИИС.

На содержимое не обращайте внимания. Хотя там и есть и акции, и облигации, в реальности я консервативен, рассчитываю на долгосрок, и растить его буду за счет ETF/БПИФ.

Ниже опишу основные элементы этой секции.

Portfolio

В левом верхнем углу дашборда я хочу видеть баланс счета крупными цифрами сразу во всех трех валютах (balance_currency).

Использую обычный виджет Stat и для каждого из полей перекрываю способ отображения (символ валюты):

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

Для получения текущей стоимости проще всего прибавить ожидаемые прибыли/убытки (tcs_yield). Поэтому везде, где нужна текущая стоимость, и используется tcs_item() + tcs_yield().

Details

Таблица с тремя query (для каждой из валют) типа

Sum(tcs_item{balance_currency="RUB"}) by (account) + Sum(tcs_yield{balance_currency="RUB"}) by (account)

И пришлось добавить Transform (убрать поле Time и поименовать колонки)

Также для красоты я переопределил суммарный аккаунт _Total_ в TOTAL

Profit/Loss

Такая же табличка, только запросы проще:

Sum(tcs_yield{balance_currency="RUB"}) by (account)

Profit/Loss without Currency

Значительная доля на брокерском счету у меня в валюте, и мне интересно отделить результат инвестирования от банальной валютной переоценки кеша. Поэтому еще одна табличка без Currency:

Sum(tcs_yield{balance_currency="RUB",account=~"T.*",type!="Currency"}) by (account)

Для ИИС видно, что портфель целиком в небольшом минусе (там до последнего момента лежали евро). При этом «w/o currency» слегка плюс.

Для долгосрочного инвестирования это все неважно, но мне было интересно посмотреть на данные с разных сторон этим новым для меня способом.

Графики

Portfolio целиком в разных валютах

Две линии: текущая стоимость портфеля (tcs_item + tcs_yield) и штрих-пунктиром инвестированная (в данном случае небольшой минус)

Sum(tcs_item{balance_currency="RUB",account=~"T.*"}) + Sum(tcs_yield{balance_currency="RUB",account=~"T.*"})

Sum(tcs_item{balance_currency="RUB",account=~"T.*"})

Portfolio w/o Currency

То же, но за вычетом кеша. Желтая линия имеет ступеньку, в то время как сам портфель целиком такой не имеет. Это потому что я не вводил дополнительные средства на счет, а просто потратил остаток свободных рублей на покупку ETF на Казначейские облигации США (запарковал кеш в ожидании, что после выборов доллар подрастет).

И раздельно по портфелям

Поскольку курс евро и доллара более-менее близки, их я «прижал» к правой оси.

К сожалению, я не знаю, как задать масштаб с «дельтой» для плавающих значений. Поэтому для валют «дельта» достаточно велика (доллар формирует нижнюю границу графика, евро - верхнюю). И их колебания выглядят незначительными. А вот в рублях «расколбас по осям» всегда от минимума до максимума, хотя на самом деле относительные изменения могут быть совсем невелики. Хотелось бы рубль также вогнать в диапазон 10-20% (как отношение евро/доллар), но не знаю как это сделать.

Currency

tcs_rate{currency=~"USDRUB"}

tcs_rate{currency=~"EURRUB"}

Item info

Вверху дашборда можно выбрать инструмент для получения информации по нему

Для таблички используется 6 query. 3 «суммы» для каждой из валют и 3 просто tcs_yield

sum(tcs_item{name="$Item",balance_currency="RUB"}) + sum(tcs_yield{name="$Item",balance_currency="RUB"})

 sum(tcs_yield{name="$Item",balance_currency="RUB"})

Для графиков текущего баланса и «инвестированного»:

sum(tcs_item{name="$Item",balance_currency="RUB"}) by (name) + sum(tcs_yield{name="$Item",balance_currency="RUB"}) by (name)

 sum(tcs_item{name="$Item",balance_currency="RUB"}) by (name)

В данном случае отображаю в рублях. Ну, тут на выбор.

Важно отметить параметр $Item. Он как раз задает инструмент, выбранный вверху. Также он используется и при формировании заголовка:

Current position and yield - $Item

Портфель (TinkoffIis)

Для графиков и таблички используется Time series, как обычно. Из интересного – группирую данные по имени инструмента (name) и в легенде укаываю {{ name }}, чтобы в табличке было соответсвующее название. В качестве альтернативы можно использовать {{ticker}}. Как вывести оба типа “PFE (Pfizer)” не знаю.

Sum здесь используется для единообразного подхода для total, если инструмент встретится в обоих.

Можно группировать по разному

Для оценки диверсификации не просто выбираем 3 query в разных валютах, но и разбиваем их по типу актива. Это позволяет отдельно увидеть, например, Currency RUB и Currency EUR.

sum(tcs_item{balance_currency="RUB",account=~"TinkoffIis", currency="RUB"}) by (type) + sum(tcs_yield{balance_currency="RUB",account=~"TinkoffIis", currency="RUB"}) by (type)

 sum(tcs_item{balance_currency="RUB",account=~"TinkoffIis", currency="USD"}) by (type) + sum(tcs_yield{balance_currency="RUB",account=~"TinkoffIis", currency="USD"}) by (type)

 sum(tcs_item{balance_currency="RUB",account=~"TinkoffIis", currency="EUR"}) by (type) + sum(tcs_yield{balance_currency="RUB",account=~"TinkoffIis", currency="EUR"}) by (type)

При этом в качестве Value используется последнее ненулевое значение (Last *), а для отображения (Legend) также и доля (Percent)

Для отдельного обзора каждого типа инструмента (ну если бы их было много разных), можно сделать отдельные Pie chart типа

sum(tcs_item{type="Etf",account=~"TinkoffIis",balance_currency="RUB"}) by (name) + sum(tcs_yield{type="Etf",account=~"TinkoffIis",balance_currency="RUB"}) by (name)

sum(tcs_item{type="Stock",account=~"TinkoffIis",balance_currency="RUB"}) by (name) + sum(tcs_yield{type="Stock",account=~"TinkoffIis",balance_currency="RUB"}) by (name)

и т.п.

Немного о грустном

Диверсификация

Хотя я и привел пример с диверсификацией портфеля, в реальности не стоит особо на нее полагаться. Это ведь обычно делается для оценки риска (скажем, 90% акции / 10% облигации – портфель с высоким риском). Однако, ETF на облигации достаточно консервативны, по идее стоит отнести их к Bond, но не представляю, как это сделать, как их отделить от ETF на акции, относящиеся все же к более рискованным активам.

Впрочем, ETF FXFA (FinEx Fallen Angels UCITS ETF - Высокодоходные корпоративные облигации развитых стран) хоть и облигации, но я бы не стал относить их к консервативным. Уверен, что в кризис они ощутимо обвалятся. Так что я не стал заморачиваться. Для этой цели все равно что-то специализированное нужно (да хоть intelinvest не так давно научился ETF по секторам раскидывать).

Другая проблема - валюты Условный Пфайзер я купил за доллары, и он реально котируется в долларах. Он значится в Stock, USD. Но есть много зарубежных инструментов, купленных за рубли. И они попадут в "рублевую часть", потому что Тинькофф вернет currency="RUB" , хотя на самом деле это валютные активы, "застрахованные" от падения курса рубля.

Банковские счета и вклады

Хочется видеть в одном дашборде также и другие средства. Уж как минимум из того же Тинькофф, только уже банка. Но увы, этот API доступен только бизнес аккаунтам. Физикам вроде его могут подключать, но в индивидуальном порядке.

Кое-кто (к примеру, Дзен-мани) умеют эту информацию извлекать. Но, насколько я понял, они используют API от мобильного клиента. Сделать reverse engineering далеко выходит за пределы моих возможностей.

Так что пока увы.

Впрочем, если когда-то и получится такое сделать, это будет отдельный контейнер для Prometheus. А Grafana все объединит :)

Заключение

Мне хотелось показать, что в Grafana можно делать довольно разнообразные и информативные дашборды даже для не совсем стандартных применений. Экспортер в Prometheus пишется легко. Даже у меня, ни разу не программиста, с этим сложностей не возникло.

Буду рад комментариям. Интересен кому-то такой подход, или это извращение? Я его затеял, чтобы на чем-то практическом потренироваться в Python/Prometheus/Grafana, и своей цели достиг. Но в практической применимости в свете проблем с диверсификацией не очень уверен. Посматривать на текущий баланс, конечно, буду. Но вот принимать решения, чего прикупить исходя из этих графиков - вряд ли.

Tags:
Hubs:
+29
Comments16

Articles