API Тиньков.Инвестиции. Первые шаги
Практически с первых дней я стал клиентом Тиньков.Инвестиции.
И с этого же момента меня терзают смутные сомнения — отражает ли личный кабинет объективную реальность?
Дело в том, что я покупаю ценные бумаги, номинированные в долларах, но в ЛК цены всех активов отображаются в долларах, а итоговая стоимость портфеля в рублях.
И мне непонятно, это доллар вырос или я такой результативный инвестор?
А как же комиссии, налоги и прочие дивиденды?
Вот бы взять все мои сделки и расписать по ФИФО, как в складском учете… А сверху положить полученные дивиденды, а потом вычесть налоги.
Вот тогда я и увижу понятный мне результат.
Оказалось, у Тинькова есть API, которое позволяет писать торговых роботов (мне это совсем не интересно), а также загружать данные по своему портфелю и операциям.
У этого API есть официальное описание, но мне не все было понятно, пришлось разбираться.
Результаты этих разборок представляю вашему вниманию.
Полезные ссылки:
Описание API
Еще описание
Получение токена и установка библиотеки
Перед началом работы нужно установить библиотеку и получить токен.
Установка библиотеки:
pip install -i https://test.pypi.org/simple/ --extra-index-url=https://pypi.org/simple/ tinkoff-invest-openapi-client
Цитирую официальную инструкцию по получению токена:
- Зайдите в свой аккаунт на tinkoff.ru
- Перейдите в раздел инвестиций
- Перейдите в настройки
- Функция «Подтверждение сделок кодом» должна быть отключена
- Выпустите токен OpenApi для биржи и Sandbox. Возможно система попросит вас авторизоваться еще раз, не беспокойтесь, это необходимо для подключения робота к торговой платформе.
- Скопируйте токен и сохраните, токен отображается только один раз, просмотреть его позже не получится, тем не менее вы можете выпускать неограниченное количество токенов.
На момент написания статью токен выдавался на странице www.tinkoff.ru/invest/settings, кнопка в нижней части страницы.
С токеном для песочницы у меня возникали ошибки, поэтому я начал экспериментировать с боевой версией. Чего и вам желаю (Осторожно: не купите-продайте случайно что-то лишнее).
Авторизация
from openapi_client import openapi
token = 'тут нужно вставить ваш токен'
client = openapi.api_client(token)
Эти две строки делают все, что нам нужно.
Дальше работаем с переменной client.
Что у нас в портфеле
Получим содержимое нашего вашего портфеля:
pf = client.portfolio.portfolio_get()
Посмотрим основные данные первого элемента:
print('value:', pf.payload.positions[0].average_position_price.value)
print('currency:', pf.payload.positions[0].average_position_price.currency)
print('balance:', pf.payload.positions[0].balance)
print('figi:', pf.payload.positions[0].figi)
print('ticker:', pf.payload.positions[0].ticker)
print('name:', pf.payload.positions[0].name)
В моем случае это:
value: 45.98
currency: USD
balance: 21.0
figi: BBG000BWPXQ8
ticker: BTI
name: British American Tobacco
value — Цена бумаги
balance — Кличество бумаг в портфеле, value и currency — их денежное выражение.
figi — Financial Instrument Global Identifier (Финансовый Глобальный Идентификатор инструмента)
ticker — Тикер актива.
По этим данным мы можем узнать человекочитаемое название актива.
Для данного запроса нам это не нужно (см. поле name), но в других случаях пригодится.
Получаем название бумаги по FIFI и тикету
# Получение инструмента по FIGI
instr = client.market.market_search_by_figi_get('BBG000BWPXQ8')
instr
Получаем:
{'payload': {'currency': 'USD',
'figi': 'BBG000BWPXQ8',
'isin': 'US1104481072',
'lot': 1,
'min_price_increment': 0.01,
'name': 'British American Tobacco',
'ticker': 'BTI',
'type': 'Stock'},
'status': 'Ok',
'tracking_id': 'a1979917d2141916'}
Эта API-функция у меня работает как надо. Видим, что 'BBG000BWPXQ8' → 'British American Tobacco'.
А вот поиск названия актива по тикеру у меня не работает :(((
instr = client.market.market_search_by_ticker_get('BTI' )
print(instr)
Разработчики предложили обновить библиотеку, но даже после этого не взлетело.
Качаем справочник ценных бумаг
Впрочем, я решил этот вопрос кардинально. Скачал у Тинькова полный справочник торгуемых активов:
# Получение списка облигаций
bonds = client.market.market_bonds_get()
# Получение списка ETF
etfs = client.market.market_etfs_get()
# Получение списка акций
stocks = client.market.market_stocks_get()
instr_list = bonds.payload.instruments + etfs.payload.instruments + stocks.payload.instruments
instr_list[:3]
получил
[{'currency': 'RUB',
'figi': 'BBG00844BD08',
'isin': 'RU000A0JU898',
'lot': 1,
'min_price_increment': 0.1,
'name': 'МКБ выпуск 9',
'ticker': 'RU000A0JU898'}, {'currency': 'RUB',
'figi': 'BBG00R05JT04',
'isin': 'RU000A1013Y3',
'lot': 1,
'min_price_increment': 0.1,
'name': 'Черкизово выпуск\xa02',
'ticker': 'RU000A1013Y3'}, {'currency': 'RUB',
'figi': 'BBG00PNLY692',
'isin': 'RU000A100DC4',
'lot': 1,
'min_price_increment': 0.1,
'name': 'МСБ-Лизинг 002P выпуск 2',
'ticker': 'RU000A100DC4'}]
Как видим, figi и name там есть. Для моих целей — более чем достаточно.
Получаем список операций
А вот самое интересное — получить список моих операций. В операции (в моем случае) попадают следующие действия:
- PayIn — Пополнение брокерского счета
- PayOut — Вывод денег
- BuyCard — Покупка с карты
- Sell — Продажа
- BrokerCommission — Комиссия брокера
- Dividend — Выплата дивидендов
- Tax — Налоги
- TaxDividend- Налоги c дивидендов
- ServiceCommission — Комиссия за обслуживание
Код для выгрузки портфеля:
from datetime import datetime
from pytz import timezone
# Качаем все операции с 30 сентября 2016 (я один из первых клиентов Тиньков Инвестиции)
d1 = datetime(2016, 9, 30, 0, 0, 0, tzinfo=timezone('Europe/Moscow')) # timezone нужно указывать. Иначе - ошибка
d2 = datetime.now(tz=timezone('Europe/Moscow')) # По настоящее время
ops = client.operations.operations_get(_from=d1.isoformat(), to=d2.isoformat())
Посмотрим, что получилось. В моем случае, представляет интерес этот элемент
ops.payload.operations[217]
Вот что он собой являет
{'commission': {'currency': 'USD', 'value': -0.42},
'currency': 'USD',
'date': datetime.datetime(2018, 11, 7, 10, 55, 53, 648913, tzinfo=tzoffset(None, 10800)),
'figi': 'BBG000PSKYX7',
'id': '42281525510',
'instrument_type': 'Stock',
'is_margin_call': False,
'operation_type': 'BuyCard',
'payment': -141.05,
'price': 141.05,
'quantity': 4,
'status': 'Done',
'trades': [{'date': datetime.datetime(2018, 11, 7, 10, 55, 53, 648913, tzinfo=tzoffset(None, 10800)),
'price': 141.05,
'quantity': 1,
'trade_id': '42636800'}]}
Нас интересуют поля:
- date — дата сделки
- figi — код актива
- operation_type — тип операции
- payment — сумма операции. У налогов или комиссий указана именно она. price при этом None
- price — цена одной бумаги
- quantity — плановое количество бумаг
- trades — реальные биржевые сделки
Сразу возник вопрос — зачем нам какие-то trades, если есть price и quantity?
Все не так просто (план и факт)
Как я понял, в quantity указано то количество бумаг, которые я хотел купить. А то, что фактически куплено, лежит в trades[i].quantity.
Т.е. если хотите обратится к фактическим сделкам, нужно перебрать то, что лежит в trades.
В ряде случаев, там None — например, для налогов или вводов/выводов средств.
Чтобы получить настоящие цифры, нужно смотреть и в сделки, и в биржевые операции:
for op in ops.payload.operations: # Перебираем операции
print(op.figi) # figi всегда берем из операции
print(op.operation_type) # и тип операции тоже
if op.trades == None: # Если биржевых сделок нет
print('price:', op.price) # Берем из операции цену бумаги
print('payment:', op.payment) # Сумму платежа
print('quantity:', op.quantity) # И количество бумаг
else:
for t in op.trades: # А если есть сделки - то перебираем их
print('price:', t.price) # И берем данные из них
print('quantity:', t.quantity)
print('--------------')