Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.

Программирование

Натхненниками розробки сервісу статистики для Тінькофф Інвестицій стали:

Про що йтиметься?

  • Тільки прикладна частина для розробки.
  • Реальні знання та досвід, який дуже важливий у роботі з фінансовими інструментами.
  • Огляд проблем з якими належить працювати

Отже, я хочу порахувати статистику з операцій і зробити це у зручному для себе вигляді. 

Розробка сервісу статистики за кроками: 

  1. Підключення до Tinkoff Invest API
  2. Відображення даних з Tinkoff Invest API у браузері
  3. Отримання брокерських звітів та операцій
  4. Підрахунок і виведення інформації, що цікавить
  5. Висновки та плани на майбутнє

Підключення до Tinkoff Invest API

Для підключення до API можна брати будь-який sdk із документації https://github.com/Tinkoff/investAPI#sdk . Або npm пакет ` tinkoff-sdk-grpc-js` . Важливо, щоб пакет був оновлений до останньої версії розробників. Встановлюємо

npm i tinkoff-sdk-grpc-js

Перевіряємо

const {createSdk} = require(‘tinkoff-sdk-grpc-js’);   // Токен, який можна отримати так  const TOKEN = ‘YOURAPI’;   // Ім’я додатку, яким вас зможуть знайти у логах ТКС. const appName = ‘tcsstat’;   const sdk = createSdk (TOKEN, appName); (async() => {     console.log(await sdk.users.getAccounts()); })();

Результат: у консоль буде виведено список рахунків. Наприклад, такий Розберемо нюанси:Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.

  • У списку рахунків є “Інвесткопилка”, з якою по API не можна працювати
  • Зверніть увагу, що поля приходять в camelCase, у той час як у документації ці поля представлені в under_score. 
  • Так буде скрізь, тож не можна просто взяти та скопіювати поле з документації.

Корисне:

  • Цей код ви можете знайти у гілці проекту

https://github.com/pskucherov/tcsstat/tree/step1 https://github.com/pskucherov/tcsstat/compare/step1   

Відображення даних з Tinkoff Invest API у браузері

Я взяв next.js та socket.io. Це не є наполегливою рекомендацією, вибирайте на власний розсуд. 

npx create-next-app@latest npm i socket.io socket.io-client

Відразу переходимо до кроку дружби next+socket+investapi, а всі деталі дивіться у розділі Корисне цього кроку.  Опишу деталі: 

  • На стороні nodejs є файл pages/api/investapi.js. Саме там створюємо сервер socket.io та підключаємося до investapi.
  • На стороні браузера (клієнта) підключаємося до сервера по сокету та запитуємо дані про рахунки у брокера. 
  • Дані від брокера отримуємо на сервєрі, після чого відправляємо на клієнт. При отриманні на клієнті вони виводяться в браузері. 

Результат:  у консолі браузера ми можемо побачити інформацію про рахунки. Тобто в минулому кроці ми інформацію про рахунки бачили в консолі сервера (nodejs), поточному кроці ми цю інформацію передали на клієнт (браузер).

Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.

Тепер зробимо так, щоб вибрати рахунок можна було з браузера, а якщо немає токена, то помилка відправлялася в консоль. Робота проста і нічого нового, тому наводжу лише посилання на коміти.

  1. https://github.com/pskucherov/tcsstat/commit/7e1ac57061e5e971588479015b06d8814d6609a9
  2. https://github.com/pskucherov/tcsstat/commit/b28ac973a57494f5232589b4cb6b9fb13b8af759 

Корисне:

  • Як подружити next та socket докладно описано тут
  • Код дружби next+socket+investapi:

Для кого складно вищевикладене, то залишаємося на даному щаблі і розуміємося на коді . Якщо є питання – ставте. https://github.com/pskucherov/tcsstat/tree/step2 https://github.com/pskucherov/tcsstat/compare/step1…step2

Отримання брокерських звітів та операцій

Для отримання брокерських звітів та операцій існує три методи

  1. GetBrokerReport
  2. GetDividendsForeignIssuer
  3. GetOperationsByCursor

З самого старту важливо знати: 

  • Брокерський звіт формується як T-3, тобто. там відображаються угоди після фактичного виконання. 
  • Відповідно, якщо ви попросите цей звіт за останні два дні, то він буде готовий через три дні. 
  • Щоб отримати угоди за останні дні використовуємо метод для отримання операцій, але пам’ятаємо, що їх id та вміст можуть змінитися після формування брокерського звіту.

GetBrokerReport

Щоб отримати брокерський звіт, потрібно взяти id рахунки, дату початку і дату кінця звіту, але не більше 31 дня. Надсилаємо запит для генерації звіту в API generate _broker_report_request , у відповідь отримати taskId. Після цього taskId отримуємо дані з get _broker_report_response.

Так говорить документація, насправді є нюанси. Слідкуємо за руками:
  • Ви повинні зберегти TaskID назавжди саме для цих дат. 
  • Так як якщо ви його втратите, то за запитані дати звіт спочатку надходитиме у відповіді на запит генерації, 
  • А потім взагалі перестане приходити.
Приступаємо до написання коду

Метод отримання дати з урахуванням віднімання від поточної дати

const getDateSubDay = (subDay = 5, start = true) => {     const date = new Date();     date.setUTCDate(date.getUTCDate() – subDay);       if (start) {         date.setUTCHours(0, 0, 0, 0);     } else {         date.setUTCHours (23, 59, 59, 999);     }       return date; };

Запит на генерацію звіту 

const brokerReport = await (sdk.operations.getBrokerReport)({         generateBrokerReportRequest: {             accountId,             from,             to,         }, });

Результат:

  • За підсумками першого виконання команди отримуємо taskId. 
  • Звіт починає генеруватися за брокера. Коли він буде готовий невідомо, чекаємо і періодично смикаємо taskId в очікуванні звіту.
  • Чому? Тому що, якщо звіт не готовий, то кидає помилку. Якщо звіт не готовий на стороні брокера, це помилка у вас в коді. Обробляйте, будь ласка: 30058 | INVALID_ARGUMENT |

Код очікування та отримання звіту виглядає приблизно так.

const timer = async time => {     return new Promise(resolve => setTimeout(resolve, time)); }   const getBrokerResponseByTaskId = async (taskId, page = 0) => {     try {         return await (sdk.operations.getBrokerReport)({             getBrokerReportRequest: {                 taskId,                 page,             },         });     } catch (e) {         console.log(‘wait’, e);         await timer(10000);         return await getBrokerResponseByTaskId(taskId, page);     } };

Далі відбувається та сама магія. Ми зупиняємо наш скрипт, запускаємо знову, taskId у нас немає. Виконуємо код із запитом taskId, але отримуємо вже не taskId, а відразу звіт. Магія! І все було б добре, якби так було завжди. Але за місяць даних не буде зовсім. Корисне :

  • Трохи теорії викладено тут і тут .
  • Збираємо код воєдино, чернетка виглядатиме приблизно так.

https://github.com/pskucherov/tcsstat/tree/step3.1 https://github.com/pskucherov/tcsstat/compare/step3.1

  • Якщо хтось із цим зіткнеться, то ласкаво просимо в їжу . Після того як полагодять ця магія втратить свою силу і буде якось інакше. Але зараз (21.03.2023) працює саме так.

GetDividendsForeignIssuer

Хтось може подумати, що метод виконаний аналогічно до попереднього і можна використовувати єдиний метод, в якому тільки змінювати назву операцій. А ось і не вгадали!  Неймінг там дуже відрізняється і в методах, і в інформації, що повертається. А відлік сторінок починається то з 0, то з 1. Щоб не заплутатися у всьому цьому, простіше написати два різні методи. Що дивно, т.к. логіка роботи однакова. Я довго плювався, коли намагався зробити один метод і було менше коду. Прикладів тут не буде.

GetOperationsByCursor

Мій улюблений метод із цієї трійці. Хоч і не найточніший, зате найадекватніший. Робимо запит від початку створення рахунку до максимально можливої ​​дати (закриття рахунку або поточної). Отримуємо відповідь, беремо курсор і перепитуємо до тих пір, поки є дані.  І код виходить лаконічнішим, ніж у прикладах вище.

const timer = async time => {     return new Promise(resolve => setTimeout(resolve, time)); }   const getOperationsByCursor = async (sdk, accountId, from, to, cursor = ”) => {     try {         const reqData = {             accountId,             from,             to,             limit: 1000,             state             : sdk.OperationState.OPERATION_STATE_EXECUTE             безвитрат: false,             безвідповідей: false,             cursor,         };         return await sdk.operations.getOperationsByCursor(reqData);     } catch (e) {         await timer(60000);           return await getOperationsByCursor(sdk, accountId, from, to, cursor = ”);     } };

Чернетка для запуску лежить тут: https://github.com/pskucherov/tcsstat/tree/step3.3 https://github.com/pskucherov/tcsstat/compare/step3.3 Тепер ми готові додати отримання операцій до нашої програми. Якщо робити правильно, потрібно отримати брокерські звіти за весь час існування рахунку. А для даних, що відсутні, тих самих Т-3, дозавантажити з операцій. Але це можна назвати окрему статтю. З основних нюансів, з якими доведеться зіткнутися – це склеїти операції та брокерський звіт.

  •  Якщо ви сьогодні отримали брокерський звіт та операції за необхідні дати, склали це все в БД, то проблем немає. 
  • Проблеми у вас будуть завтра, коли ви отримаєте наступну порцію даних зі звіту та операцій та вирішите їх синхронізувати з наявною БД. 
  • Дуже багато нюансів для незбігаються або змінюються id після обробки
  • Потім для позабіржового ринку ID не збігаються зовсім.
  •  А також нюансів із синхронізації інструментів, які знову таки не збігаються, через особливості API. Але то вже інша історія.

Додамо отримання інформації про операції у наш додаток. Основним питанням буде те, де оброблятимуться та зберігатимуться дані.

  •  Якщо ви робите для себе, будете з різних пристроїв споживати ті самі дані. То вам потрібно обробляти та зберігати дані на сервері.
  • Якщо у вас буде безліч різних даних споживати багато різних користувачів, потрібно приймати рішення, що важливіше: швидкість у користувачів або економія заліза на вашій стороні. Хто може дозволити собі нескінченну кількість заліза, той вважає все на сервері і робить користувачам супер швидко, економлячи користувачеві ресурси, наприклад батарею і трафік, що на телефонах буває дуже важливо.

У свою чергу, вважати в браузері не оптимальне рішення в принципі. Тому що не дорого, то рахуємо у себе на сервері. Решту виносимо на клієнт. Дуже хочеться взяти та порахувати комісію на сервері. Але тут приходить нюанс під назвою “інтерактивність”. Припустимо, у вас тисячі операцій та на їх отримання потрібно п’ять хвилин. Що буде в цей час користувача? Спіннер? Прогрес? Інфо про те скільки було завантажено? Ідеально використовувати “активне очікування”, коли користувач у процесі міг щось побачити. Ось так Результат:Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.

  • Завантажується сторінка
  • Запитуються всі рахунки
  • Після чого всім рахунків запитуються всі операції з комісіями за виконані угоди. У міру отримання даних вони відображаються у браузері.

Щоб не фільтрувати щоразу дані в подіях, для кожного рахунку митуємо свою подію. Ось так:

socket.emit(‘sdk:getOperationsCommissionResult_’ + accountId, {                 items: data?.items,                 inProgress: Boolean(nextCursor), });

Чернетка для запуску лежить тут: https://github.com/pskucherov/tcsstat/tree/step3 https://github.com/pskucherov/tcsstat/compare/step2…step3 Рухаємося далі. Здорово, що ви дочитали до цього рядка! 

Підрахунок і виведення інформації, що цікавить

Залежить від того, кому якась інформація потрібна. Тому одразу розповідаю основні нюанси, з якими ви зіткнетеся.

Робота з цінами 

Усі, хто працює з фінансами, знають, що фінансові операції треба виконувати лише з цілими числами. Через неточність значень після коми і помилки, що накопичується при великій кількості операцій. Саме тому всі ціни представлені у наступному форматі MoneyValueРозробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.

FieldTypeDescription
currencystringСтроковий ISO-код валюти
unitsint64Ціла частина суми може бути негативним числом
nanoint32Дробова частина суми може бути негативним числом

Обробляємо їх окремо, потім наводимо до значення ціни:

quotation.units + quotation.nano / 1e9

Вартість ф’ючерсних контрактів

Ціна ф’ючерсів представлена ​​у пунктах, коли у вас валютний ф’ючерс треба знати курс. І, звичайно ж, ціну в пунктах і крок ціни. Коли вважатимете прибуток від угод це може вистрілити, т.к. якщо рахувати загальну суму перемножуючи прайс на кількість. Тут треба акуратніше. Поки що так, як буде далі – побачимо. Це стосується валютних ф’ючерсів, в інших місцях із цим все бл.Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.

Позабіржовий ринок

У цього ринку багато особливостей, тому окремо досліджуємо операції по ньому. Коли почнете синхронізувати операції, то виявиться, що треба приводити figi/ticker до одного виду, щоб правильно зіставити інструмент. Коли почнете синхронізувати це з брокерським звітом, то виявиться, що в tradeID у однієї і тієї ж операції є на початку літери в операціях і їх немає в брокерському звіті. Тому їх не можна порівняти… кхм-кхм… порівнянням! Я зіставляв на час угоди, тикер та матчинг що один tradeId міститься у іншому. Як правильно не знаю. Хто зіткнеться з цим і кому це важливо, приходьте в їжу або заводьте новий.Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.

Математичні операції над інструментами

Не можна, не дивлячись, робити математичні операції з усім списком. Щоб не складати тепле з м’яким, завжди перевіряємо валюту та обробляємо лише, якщо переконалися, що валюта збігається, а пункти переведені у потрібну валюту. Озброївшись знаннями про роботу з банківськими числами, порахуємо витрачену комісію по кожному з рахунків. Ось так: https://github.com/pskucherov/tcsstat/tree/step4 https://github.com/pskucherov/tcsstat/compare/step3…step4Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.   

Мікросервіс готовий!

https://github.com/pskucherov/tcsstat Як домашнє завдання можете перевірити чи працює сервіс при повільному з’єднанні, при обриві з’єднань, при відключенні інтернету, при помилках або лімітах, що закінчилися, з боку брокера. 

Висновки та плани на майбутнє

  • Дізналися про базові операції та роботу з Invest API
  • Витрачений час ~ 10 годин
  • Рівень складності ~ junior+ / low middle 

Якщо продовжувати мікросервіс доопрацьовувати, то може вийти щось на кшталт цього

https://opexbot.info

  Це моя розробка, для тих кому ліньки розбиратися, запускати і рахувати самостійно. Планую додати туди аналітику на прохання тих, хто використовує. Якщо вам сподобалася стаття, підписуйтесь на мій телеграм канал . Розробляємо мікросервіс використовуючи Tinkoff Invest API для автоматизації роботи з брокерськими звітами та підрахунку комісій.

Pavel
Оцініть автора
Додати коментар

  1. Isakiiev

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

    Відповіcти