Продолжаем ковырять Тинькофф API. Рассчитываем эффективность инвестиций

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

Цель проекта — повысить прозрачность при работе с инвестициями, чтобы держать все свои финансовые потоки под контролем, что должно сделать инвестирование более осознанным, а следовательно, более эффективным.

Что было сделано в первой версии

После непродолжительного ддоса API Тинькофф и ЦБ, в папке с программой появлялся файлик .xlsx, содержащий на одном листе:

  • Курсы валют: текущий рыночный курс и ЦБ

  • Список позиций в портфеле и 14 колонок с данными по каждой позиции

  • Расчёт налога, который светит инвестору при закрытии позиций (без учёта налоговых льгот)

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

  • Инвестиционный период (сколько лет, месяцев, дней мы этим занимаемся)

  • Разницу между заведёнными на счёт средствами и выведенными

  • Сумму всех уже уплаченных налогов и комиссий (может не порадовать активных трейдеров)

  • Баланс портфеля, очищенный от налогов — сколько мы получим, если прямо сейчас всё продадим. Не учитывает комиссии брокера и тот налог, который уже мог набежать по закрытым ранее в этом году позициям.

  • Профит — разница между чистым балансом портфеля и вложенными средствами.

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

Список всех комиссий за обслуживание
Список всех комиссий за обслуживание

После некоторой дискуссии, в поддержке признали ошибку и вернули сумму.

Победа.
Победа.

Мне всё равно нравится Тинькофф, по-моему это один и самых передовых банков в мире, хотя бы потому, что у него есть этот API, благодаря которому вообще возможно данное обсуждение.

При этом, всякое бывает, финансы требуют внимательного учёта.

Поддержка сообщества

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

Так, разработчик под ником idsulik быстро пофиксил мой баг с делением на 0, на который многие стали жаловаться сразу после публикации.

Другой разработчик, sergey-volohin, вообще перелопатил весь мой код, существенно его оптимизировав.

Скриншот с GitHub
Скриншот с GitHub

Ещё Сергей заменил принтинг на логгинг, который выглядит более профессионально.

Я почерпнул от него много нового и полезного для себя.

Так вот как по-английски пишется “костыль”
Так вот как по-английски пишется “костыль”

Это был мой первый небольшой опыт совместной разработки, и я почувствовал, что так делать проекты намного веселее.

Кроме непосредственных коммитов (commits) пользователи GitHub и Хабр стали выдвигать свои идеи и замечания.

Проблемы и замечания

Помимо упомянутой ошибки деления на 0, в программе обнаружилось ещё несколько проблем.

Во-первых, оказалось, что программа вылетает, если в портфеле есть бумаги, полученные бесплатно. Таких бумаг обнаружилось два вида.

Первые появляются в результате каких-то корпоративных действий, в моём случае так произошло с акциями Organon & Co, появившимися в портфеле в результате дробления Merk.

Скриншот приложения Тинькофф Инвестиции. Organon & Co всегда показывает 0 $ (0 %)
Скриншот приложения Тинькофф Инвестиции. Organon & Co всегда показывает 0 $ (0 %)

Цена покупки нулевая, expected yield всегда будет 0, из-за этого в расчётах возникала ошибка, но исправить её оказалось не сложно. В результате, все недостающие ячейки в таблице просто заполняются нулями.

Второй вид дарёных бумаг выдаётся брокером за приведённого друга.

Ради эксперимента я продал пригласил в Тинькофф свою мать, чтобы она открыла себе ИИС. За её душу мне выдали 1 акцию L Brands.

Данная бумага есть в приложении, но отсутствует в API.
Данная бумага есть в приложении, но отсутствует в API.

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

Другая проблема оказалась с акциями самого Tinkoff. Об этом я узнал от других пользователей и купил эту бумагу себе в портфель, чтобы проверить. Оказалось, что в Tinkoff API акции TCSG и операции по этой бумаге почему-то имеют разные figi, что приводило к ошибке. Пришлось написать отдельный костыль специально для акций TCSG.

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

Начало и продолжение таблицы с налогами. Каждая строчка — одна бумага (тут не все). Внизу — итоги
Начало и продолжение таблицы с налогами. Каждая строчка — одна бумага (тут не все). Внизу — итоги
Исходя из таблицы, если я сейчас закрою все позиции, то должен буду заплатить налог в размере 176 506 рублей 66 копеек, без учёта налоговых льгот и возможных налогов по ранее закрытым позициям
Исходя из таблицы, если я сейчас закрою все позиции, то должен буду заплатить налог в размере 176 506 рублей 66 копеек, без учёта налоговых льгот и возможных налогов по ранее закрытым позициям

По мере этих доработок и прочих допиливаний, мне всё больше стала досаждать другая проблема — увеличение времени работы программы. Чем больше было в портфеле позиций и совершённых операций — тем медленнее всё работало. При моём портфеле из 20-25 позиций и 450 операций, после всех нововведений подготовка отчёта стала занимать более 10 минут. А что если у более активного инвестора в портфеле 100 позиций и десятки тысяч операций? Надо было что-то делать, хотя бы потому, что при такой скорости работы программы очень неудобно отлаживать изменения. Внёс одну правку — уходишь заваривать чай.

Я предположил, что, возможно, программа обращается к ЦБ за курсами валют по нескольку раз для одной и той же даты, если на эту дату приходится несколько операций. Тогда я поменял алгоритм таким образом, чтобы программа сразу один раз опросила ЦБ по каждой дате за весь период инвестирования, сохранила в массив и больше ЦБ не мучала. Ход сработал, и время работы программы сократилось, в моём случае, до 2 с половиной минут, т.е. примерно в 4 раза.

Но всё равно, этого было недостаточно. Стала очевидна нерациональность самого подхода. Я запускал программу по 15 раз в день, и каждый раз она, в течение 2х минут, по новой собирала с ЦБ те же самые данные за все 3 года инвестирования.

Подключаем базу данных

Новые товарищи с Хабра давно советовали мне прикрутить MySQL. Я раньше уже немного сталкивался этой базой данных, когда делал небольшой проект с интернетом вещей, чтобы хранить на удалённом сервере данные с контроллеров ESP и иметь к ним доступ через свой сайт. Но в этот раз я решил от неё отказаться, чтобы ни кого не обременять установкой дополнительного ПО (хотя, может это и не обязательно, если честно, не стал до конца вникать).

Мне нужно было самое простое решение для того чтобы сохранить собранные значения курсов валют и при последующих запусках программы находить их по датам. А если новой даты ещё нет — запросить в ЦБ и дописать в базу.

Я остановился на файле CSV (Comma-separated values).

Дата, USD, EUR, RUB
Дата, USD, EUR, RUB

Оказалась очень простая и удобная штука.

Подключаем модуль: import csv

Вот так считываем:

day_rates = {}
with open('rates_by_date.csv', 'r') as file:
   reader = csv.reader(file)
   # creating a dictionary from csv:
   for row in reader:
       date = datetime.strptime(row[0], '%Y-%m-%d').date()
       usd = decimal.Decimal(row[1])
       eur = decimal.Decimal(row[2])
       rub = decimal.Decimal(row[3])
       day_rates.update({date: {'USD': usd, 'EUR': eur, 'RUB': rub}})

Вот так записываем:

with open('rates_by_date.csv', 'w', newline="") as file:
   writer = csv.writer(file)
   for date in day_rates.keys():
 	    writer.writerow([date, day_rates[date]['USD'],
                             day_rates[date]['EUR'],
                             day_rates[date]['RUB']])

В итоге, после первого заполнения базы, время работы программы сократилось до пары секунд. А сам файлик с данными за 3 года весит 33KB и при желании редактируется блокнотом.

Но товарищи фанаты MySQL всё равно меня заминусуют.

Реализация предложений по доработке

Разобравшись с основными проблемами, я приступил к расширению функционала программы.

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

Спасибо Тинькофф банку за комментарии!
Спасибо Тинькофф банку за комментарии!

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

Ещё на GitHub поступила идея насчёт использования банковское округления. Идея была в том, чтобы уменьшить расхождение некоторых данных в таблице относительно данных в приложении, особенно это касается общей стоимости портфеля. В первой статье я отмечал непонятное расхождение в 0,0077% для моего портфеля.

При банковском округлении, нечётное число (до запятой) округляется в большую сторону, а чётное в меньшую. Оказывается, Python почему-то предпочитает именно такое округление. Например: round(1.5) будет равно 2, а round(2.5) тоже окажется равно 2, а не 3, как может показаться. Способ округления можно изменить, используя decimal .

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

market_cost = this_pos.expected_yield.value + (this_pos.average_position_price.value * this_pos.balance)

expected_yield — ожидаемый доход в случае закрытия позиции

average_position_price средняя цена покупки одной бумаги

balance количество бумаг

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

def get_current_market_price(figi):
   client = tinvest.SyncClient(account_data['my_token'])
   book = client.get_market_orderbook(figi=figi, depth=20)
   price = book.payload.last_price
   return price

Затем, market_price умножается на balance (количество бумаг) и получается точно такая же стоимость позиции, как и в приложении.

Но оказалось, что это не работает для облигаций, потому что book.payload.last_price выдаёт рыночную цену облигации без НКД. Пришлось для облигаций оставить старую формулу. Но за счет остальных бумаг, точность всё равно выросла, хотя и зависит от доли облигаций в портфеле. Теперь по моему портфелю отклонение составляет примерно 3 рубля на 9 000 000, если не считать подаренную акцию L Brands.

Крутая идея поступила от ещё одного пользователя GutHub:

Скрин с GitHub
Скрин с GitHub

Сделать это оказалось не сложно, зато, согласитесь, насколько информативнее стала таблица, и какие новые возможности для последующего анализа теперь открываются:

Добавил колонку ticker - стало намного понятнее
Добавил колонку ticker — стало намного понятнее

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

Добавил новый лист
Добавил новый лист

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

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

На скриншоте поместился только текущий год, а в таблице у меня справа ещё 2020, 2019 и 2018
На скриншоте поместился только текущий год, а в таблице у меня справа ещё 2020, 2019 и 2018

Конечно, программа не учитывает возможный налог 3%, который нужно декларировать и уплачивать самостоятельно, если подписана форма W-8BEN. Кстати, у меня её всё-таки приняли. А ещё выяснилось, что в какой-то период форма у меня действовала автоматически, и один дивидендик мне таки успел капнуть с налогом 10%, из-за чего мне пришлось подавать декларацию по всем поступлениям за целый год.

Налог с дивидендов иностранных компаний.
Скриншот из личного кабинета nalog.ru
Налог с дивидендов иностранных компаний.
Скриншот из личного кабинета nalog.ru

Ещё оказалось, что в API (и мобильном приложении) бывают операции типа Tax Coupon без указания бумаги.

Вот и пойми, за что
Вот и пойми, за что

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

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

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

Но оставалось сделать самое важное для меня улучшение, которое я задумал ещё вначале проекта.

Расчет эффективности инвестирования

Моему инвестированию с Тинькофф недавно стукнуло 3 года. Ещё не много, но уже достаточно, чтобы подвести промежуточную черту и посмотреть на результат.

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

При этом, очевидно, что я пока в хорошем плюсе. Вопрос: а насколько хорошем? Процент изменения на главной странице приложения (в моей таблице % change) показывает только изменение по открытым позициям. Показатель Profit из таблицы — это практически чистый финансовый результат. Но сколько это процентов годовых? Я же периодически пополнял счёт, первые год-два у меня там работали гораздо меньшие суммы, чем сейчас. При этом, я ещё и выводил средства иногда.

Данная проблема уже поднималась на Хабре.

Одним из решений является формула Excel под названием XIRR (от англ. internal rate of return). В русской версии: ЧИСТВНДОХ.

Об этой формуле я упоминал ещё в первой статье, только не знал, как её прикрутить. В комментариях Евгений Трофимов @VSOP_juDGe подсказал решение:

pip install xirr

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

Результат по моему портфелю сейчас получается в диапазоне 20-22%, значение меняется в течение торговой сессии.

Программа показывает доходность портфеля 21.79% годовых в рублях
Программа показывает доходность портфеля 21.79% годовых в рублях

Допустим, что доходность составляет 21% годовых в рублях. Это много или мало?

Познаем в сравнении

Рублёвый депозит в банке здесь явно проигрывает. В течение этих трёх лет максимальная процентная ставка в крупных банках колебалась в диапазоне примерно от 4,3% до 7,75%.

Источник: https://www.cbr.ru/statistics/avgprocstav
Источник: https://www.cbr.ru/statistics/avgprocstav

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

Валютный депозит мог бы приносить что-то порядка 2%. Но за эти 3 года USD вырос с 63 до 73 рублей, т.е., в среднем рос примерно на 5,3% в год. Получается результат примерно как и в первом случае. Да, есть ещё капитализация и банки с более высокими процентными ставками. Но бегать и раскладывать большие суммы по 5 разным банкам, чтобы не превысить страхуемые 1,4 млн, следить за сроками окончания, потом перекладывать в другие банки, сидеть в очередях и мотаться по городу с пачками наличности, чтобы не платить конские комиссии за перевод — то ещё развлечение, проходили в своё время.

Акции российских компаний за это время пережили взлёты и падения. В целом, индекс МосБиржи за 3 года вырос с 2315 до 3805 пунктов, что составляет, в среднем 21,45% в год, что примерно соответствует и моему финансовому результату. Только этот индекс, в отличие от меня, не платил налоги и комиссии.

График индекса МосБиржи. Источник: https://investfunds.ru/indexes/216
График индекса МосБиржи. Источник: https://investfunds.ru/indexes/216

Индекс S&P500 включает 500 топовых американских компаний и обогнать его на длинной дистанции — это, наверное, как бегом обогнать верблюда в пустыне. За 3 года он вырос с 2821 до 4837 пунктов, те есть в среднем прибавлял по 23,82%. Не намного больше, чем индекс МосБиржи, только американские компании торгуются в USD, который, сам по себе прибавлял по 5,3% в год к рублю.

График S&P 500. Источник: https://ru.tradingview.com
График S&P 500. Источник: https://ru.tradingview.com

Выходит, мои угадывания с поиском интересных рекомендаций и выбором перспективных отраслей привели лишь к существенному отставанию от топового индекса. Мой 21% против примерно 29% годовых в рублях у S&P500. (Если я правильно посчитал, поправьте, пожалуйста если что).

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

Очевидно, что с ценами на жильё всё очень сложно: разные районы, типы домов, состояние ремонта, реновация, юридические аспекты, работа риэлтора и т.д.

Если верить графику на сайте с многообещающим названием: “Индикаторы рынка недвижимости”, средняя цена на квадратный метр в Москве за 3 года выросла со 167 000 до 231 000 рублей. Это примерно 12,8% в год.

При этом, квартиру ещё можно сдавать и получать, допустим, 5% её стоимости каждый год. Смотря как сдавать, конечно.

Источник: https://www.irn.ru
Источник: https://www.irn.ru

Таким образом, за 3 года, инвестиционная квартира в Москве, приобретённая без заёмных средств, могла бы дать.. очень по-разному, может кто-нибудь напишет в комментариях свой опыт, а я пока приму, что в среднем 18% годовых. Наверное, в профессиональных руках, могла бы дать и куда больше, чем мои +/- 21%. Но для меня ещё важную роль играет мобильность капитала, чтобы его можно было легко пополнять, выводить, дробить, переводить из одного актива в другой. А недвижимость — на то она и недвижимость.

Вывод

Похоже, что последние 3 года оказались “тучными” почти для всех видов инвестиций. Даже рандомно собранный портфель из бумаг крупных компаний разных отраслей, скорее всего, дал бы неплохую доходность порядка 20%, если его не теребить. Только крайне консервативные инструменты, такие как банковские депозиты и государственные облигации привели бы к доходности чуть выше инфляции. Рискованные активы могли привести как к огромной доходности, так и к убытку.

Лично я для себя сделал вывод, что инвестиции важнее выбирать под свою ситуацию. Если вы формируете себе пассивный доход на пенсию — вам, наверное, подойдут облигации и дивидендные акции. Кого-то семейное положение может подталкивать к приобретению квартиры. Если вы человек молодой, возможно, вам сильнее окупятся инвестиции в себя: в своё образование, здоровье. Если есть идея для бизнеса — ещё лучше. Можно комбинировать.

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

Кстати, я в Пульсе тоже есть под ником @softandiron. Там можно обсудить обновления программы, а ещё там есть кнопочка для доната, если мой труд вам как-то пригодился.

В планах

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

Один товарищ настроил систему на ежедневный сбор аналитики с автоматическим складыванием отчётов в папки по дням и по месяцам, и ещё настроил Google таблицы. Может, поделится в комментариях.

Я ещё хотел сделать .exe файл, но не знаю насчёт вариантов его безопасного распространения. Сам факт его появления может породить подделки. Да и аккаунты на GitHub, я слышал, угоняли. Думаю, что через украденный API key средства себе не выведешь, но если наворовать много аккаунтов, мошенник, наверное, может придумать какой-нибудь пузырь на рынке и нажиться на этом. Так что, в этой деликатной финансовой теме — безопасность должна быть на первом месте.

Ещё есть интересная идея сделать расчёт срока владения активом, чтобы отмечать бумаги, находящиеся в портфеле более 3-х лет и подпадающие под налоговую льготу.

В общем, буду рад продолжить обсуждение данной темы, следите за обновлениями.

Свежая версия программы доступна на GitHub.


Дата-центр ITSOFT — размещение и аренда серверов и стоек в двух дата-центрах в Москве. За последние годы UPTIME 100%. Размещение GPU-ферм и ASIC-майнеров, аренда GPU-серверов, лицензии связи, SSL-сертификаты, администрирование серверов и поддержка сайтов.

Source link

Добавить комментарий

Ваш адрес email не будет опубликован.