Распрацоўваем мікрасэрвіс выкарыстоўваючы 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|task не выконваецца, няхай ён ідзе латар

Код чакання і атрымання справаздачы выглядае прыкладна так.

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_EXECUD             withoutTrades: false,             withoutOvernights: 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
Rate author
Add a comment

  1. Isakiiev

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

    Reply