Готовимся резать лосей в портфеле
Nov 5, 2022 13:02 · 763 words · 4 minute read
Скоро декабрь - самая пора резать лосей. Не тех, за которыми надо охотиться, а которые у нас в брокерском портфеле (loss - минусовые позиции). Тем более, что год выдался удивительным на события, и отрицательных позиций, уверен, много у каждого. Вот и я решил узнать что можно продать с минимальным убытком. Считать ручками как-то лень, так что весь код на github.
Немного теории
Наш налоговый кодекс допускает некоторую оптимизацию уплаты. Первая попавшаяся статья говорит нам, что можно провернуть много чего, но я пользуюсь только парой пунктов:
- не платить налог с прибыли, если владеете бумагами больше 3х лет (срок владения)
- зафиксированный убыток уменьшает налогооблагаемую базу от прибыльных сделок и снижает НДФЛ (фиксация убытка)
Продать акции, которые держу больше 3х лет, я всегда успею. Так что остановлюсь на оптимизации фиксации убытка. Но чтобы понимать что продавать хочется видеть график - когда, сколько и почём я покупал. К сожалению, мой брокер предоставляет такую справку только в виде плоского списка в html. Ни о какой визуализации речь не идёт. Более того, нужно организовать и партионный учёт, чтобы исключить уже проданные акции. Что ж, пришла пора звать тыжпрограммиста.
Решение задачи
Необходимо по данным в отчёте построить график покупок и актуальных остатков. Для начала спарсим всю информацию из html в удобный формат. Например, такой:
@dataclasses.dataclass
class Transaction:
timestamp: datetime
reason: bool # True - покупка, False - продажа
count: int
price: Decimal
Portfolio = dict[str, list[Transaction]]
Не буду приводить код парсинга html, т.к. он будет отличаться от брокера к брокеру. Суть в том, что на выходе у нас должен получиться словарь с ключами - тикерами и значением из списка сделок.
Партионный учёт
Самое интересное - реализация партионного учёта. Эта такая штука в бухгалтерском деле, которая считает когда был продан товар из ранее купленной партии. На примере:
- 29.08 купили 10 акций GAZP по 184 руб.
- 30.08 купили ещё 10 акций по 200 руб.
- 27.09 продали 5 акций по 214 руб., зафиксировав прибыль в (214-184)*5 = 150 руб.
- 30.09 продали 10 акций по 235 руб., зафиксировав прибыль в [(235-184)*5 = 255] + [(235-200)*5 = 175] = 430 руб.
Т.е. в последней продаже мы продали 5 акций из первой партии и 5 из второй. Т.е. фактически у нас осталось 5 акций, купленных 30.08 по 200 руб. Остальные можно уже и не учитывать. С продажей в минус такая же история. За полчаса набросал алгоритм:
- идём подряд по партиям (сделкам)
- если это продажа, то:
- вычитаем кол-во проданных акций из сделок покупок
- отбрасываем сделки-покупки, в которых акций осталось 0
- возвращаем подсписок, состоящий только из сделок-покупок, в которых кол-во акций > 0
def partial_accounting(deals: list[Transaction]) -> list[Transaction]:
# не очень хорошая идея менять входящий массив
# можно воспользоваться deepcopy
start_item = 0
try:
for item in deals:
if not item.reason: # если продажа
sell = item.count # сколько осталось вычесть из партий
while sell > 0:
left = deals[start_item].count # акций осталось в партии-покупке
current = left - sell
if current > 0:
# в партии-покупке не всё исчерпали -> обновим количество и выходим
sell = 0
deals[start_item].count = current
else:
# партия-покупка распродана полностью
sell = sell - left # обновляем сколько надо распродать
start_item += 1
while not deals[start_item].reason:
# ищем следующую партию-покупку
start_item += 1
return [deal for deal in deals[start_item:] if deal.reason]
except IndexError:
# всё распродали
return []
Выглядит как неплохая задача на алгоритмическую секцию в интервью. В итоге у нас должен получиться список только из покупок с указанием даты, количества и цены. Для наглядности отобразим это на графике.
Простая визуализация
Из средств визуализации первое, что приходит на ум при работе с графиками - библиотека matplotlib. До текущего момента с ней не работал, так что за правильность использования API не отвечаю. Хочется на графике видеть покупки в виде кружочков, диаметр которых пропорционален количеству. Получилась такая функция:
def plot(key: str, deals: list[Transaction]):
x_values = [x.timestamp.strftime('%d.%m.%Y') for x in deals]
y_values = [float(x.price) for x in deals]
sizes = [x.count for x in deals]
fig, ax = pyplot.subplots()
ax.scatter(x_values, y_values, s=sizes) # непосредственно расставляем точки
fig.autofmt_xdate() # выравниваем даты для читаемости
fig.set_size_inches(15, 10) # увеличиваем масштаб
pyplot.savefig(f'plots/{key}.png')
pyplot.close(fig) # не забываем закрыть внутренний буфер
И вот такой график:
По нему видно, что если я продам всю первую партию, то зафиксирую убыток в 350*k руб. С этого я сэкономлю 350*k*0.13 руб. налогов. Хоть что-то приятное. При этом ещё существенно скорректируется средняя, и психологически станет чуть легче.
Резюме
Тыжпрограммист снова нашёл себе применение. Вероятно, у вашего брокера уже есть подобный функционал, но не сложно его и самому допилить. Что вам делать с этой информацией я не знаю :) Моё дело алгоритмы для решения задач писать и подавать информацию в удобном виде. Всем поменьше рогатых на бирже.