Натхняльнікамі распрацоўкі сэрвісу статыстыкі для Тинькофф Інвестыцый сталі:
- артыкул на хабры “Што недагаворваюць Тинькофф Інвестыцыі”
- аналіз пажаданняў карыстальнікаў платформы
- артыкул пра падлік камісій .
- Пра што гаворка пойдзе?
- Распрацоўка сэрвісу статыстыкі па кроках:
- Падключэнне да Tinkoff Invest API
- Адмалёўка дадзеных з Tinkoff Invest API у браўзэры
- Атрыманне брокерскіх справаздач і аперацый
- GetBrokerReport
- Метад атрымання даты з улікам аднімання ад бягучай даты
- Запыт генерацыі справаздачы
- Вынік:
- GetDividendsForeignIssuer
- GetOperationsByCursor
- Падлік і вывад цікавіць інфармацыі
- Праца з коштамі
- Кошт ф’ючэрсных кантрактаў
- Пазабіржавы рынак
- Матэматычныя аперацыі над інструментамі
- Мікрасэрвіс гатовы!
- Высновы і планы на будучыню
- https://opexbot.info
Пра што гаворка пойдзе?
- Толькі прыкладная частка пра распрацоўку.
- Рэальныя веды і досвед, які вельмі важныя ў працы з фінансавымі інструментамі.
- Агляд праблем з якімі трэба будзе працаваць
Такім чынам, я хачу палічыць статыстыку па здзелках і зрабіць гэта ў зручным для сябе выглядзе.
Распрацоўка сэрвісу статыстыкі па кроках:
- Падключэнне да Tinkoff Invest API
- Адмалёўка дадзеных з Tinkoff Invest API у браўзэры
- Атрыманне брокерскіх справаздач і аперацый
- Падлік і вывад цікавіць інфармацыі
- Высновы і планы на будучыню
Падключэнне да 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()); })();
Вынік: у кансоль будзе выведзены спіс вашых рахункаў. Напрыклад, такі Разбяром нюансы:
- У спісе рахункаў ёсць “Инвесткопилка”, з якой па 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), у бягучым кроку мы гэтую інфармацыю перадалі на кліент (браўзэр).
Зараз зробім так, каб абраць рахунак можна было з браўзэра, а калі няма токена то памылка адпраўлялася ў кансоль. Праца простая і нічога новага, таму прыводжу толькі спасылкі на коміты.
- https://github.com/pskucherov/tcsstat/commit/7e1ac57061e5e971588479015b06d8814d6609a9
- 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
Атрыманне брокерскіх справаздач і аперацый
Для атрымання брокерскіх справаздач і аперацый існуе тры метаду
З самага старту важна ведаць:
- Брокерская справаздача фармуецца ў рэжыме 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. Але гэта ўжо іншая гісторыя.
Дадамо атрыманне інфармацыі аб аперацыях у наша дадатак. Асноўным пытаннем будзе тое, дзе будуць апрацоўвацца і захоўвацца даныя.
- Калі вы робіце для сябе, будзеце з розных прылад спажываць адны і тыя ж дадзеныя. То вам трэба апрацоўваць і захоўваць дадзеныя на сэрвэры.
- Калі ў вас будзе мноства розных дадзеных спажываць шмат розных карыстальнікаў, то трэба прымаць рашэнне, што важней: хуткасць у карыстальнікаў або эканомія жалеза на вашым баку. Хто можа дазволіць сабе бясконцую колькасць жалеза, той лічыць усё ў сябе на серверы і робіць карыстальнікам супер хутка, эканомячы карыстачу рэсурсы, напрыклад батарэю і трафік, што на тэлефонах бывае вельмі важна.
У сваю чаргу, лічыць у браўзэры не самае аптымальнае рашэнне ў прынцыпе. Таму што не дорага, тое лічым у сябе на серверы. Астатняе выносім на кліент. Вельмі жадаецца ўзяць і палічыць камісію на серверы. Але тут прыходзіць нюанс пад назвай “інтэрактыўнасць”. Дапусцім, у вас тысячы аперацый і на іх атрыманне трэба пяць хвілін. Што будзе ў гэты час у карыстальніка? Спінар? Прагрэс? Інфа пра тое колькі было загружана? Ідэальна выкарыстоўваць “актыўнае чаканне”, калі карыстальнік у працэсе ўжо мог нешта ўбачыць. Вось так Вынік:
- Загружаецца старонка
- Запытваюцца ўсе рахункі
- Пасля чаго для ўсіх рахункаў запытваюцца ўсе аперацыі з камісіямі за выкананыя здзелкі. Па меры атрымання дадзеных яны адмалёўваюцца ў браўзэры.
Каб не фільтраваць кожны раз дадзеныя ў падзеях, для кожнага рахунку торгаем сваю падзею. Вось так:
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
Field | Type | Description |
---|---|---|
currency | string | Радковы ISO-код валюты |
units | int64 | Цэлая частка сумы, можа быць адмоўным лікам |
nano | int32 | Дробная частка сумы, можа быць адмоўным лікам |
Апрацоўваем іх асобна, затым прыводзім да значэння кошту:
quotation.units + quotation.nano / 1e9
Кошт ф’ючэрсных кантрактаў
Кошт ф’ючэрсаў прадстаўлена ў пунктах, калі ў вас валютны ф’ючэрс, трэба ведаць курс. І вядома ж цану ў пунктах і крок цаны. Калі будзеце лічыць профіт ад здзелак гэта можа стрэліць, т.я. калі будзеце лічыць агульную суму перамножваючы прайс на колькасць. Тут трэба акуратней. Пакуль так, як будзе далей – паглядзім. Гэта датычыцца валютных ф’ючэрсаў, у іншых месцах з гэтым усё ок.
Пазабіржавы рынак
У гэтага рынка шмат асаблівасцяў, таму асобна даследуем аперацыі па ім Калі пачнеце сінхранізаваць аперацыі, тое апынецца што трэба прыводзіць figi/ticker да аднаго ўвазе, каб правільна супаставіць прыладу. Калі пачнеце сінхранізаваць гэта з брокерскай справаздачай, то апынецца што ў tradeID у адной і той жа аперацыі ёсць у пачатку літары ў аперацыях і іх няма ў брокерскай справаздачы. Таму іх нельга параўнаць… кхм-кхм… параўнаннем! Я супастаўляў на час здзелкі, Біржавы сімвал і матчынг што адзін tradeId змяшчаецца ў іншым. Як правільна, не ведаю. Хто сутыкнецца з гэтым і каму гэта важна, прыходзьце ў ішью або заводзіце новы.
Матэматычныя аперацыі над інструментамі
Нельга, не гледзячы, рабіць матэматычныя аперацыі з усім спісам. Каб не складаць цёплае з мяккім, заўсёды правяраем валюту і апрацоўваем толькі, калі пераканаліся, што валюта супадае, а пункты пераведзены ў патрэбную валюту. Узброіўшыся ведамі пра працу з банкаўскімі лікамі палічым затрачаную камісію па кожным з рахункаў. Вось так: https://github.com/pskucherov/tcsstat/tree/step4 https://github.com/pskucherov/tcsstat/compare/step3…step4
Мікрасэрвіс гатовы!
https://github.com/pskucherov/tcsstat У якасці хатняга задання можаце праверыць ці працуе сэрвіс пры павольным злучэнні, пры абрыве злучэнняў, пры адключэнні інтэрнэту, пры памылках або якія скончыліся лімітах са боку брокера.
Высновы і планы на будучыню
- Даведаліся пра базавыя аперацыі і працу з Invest API
- Выдаткаваны час ~ 10 гадзін
- Узровень складанасці ~ junior+ / low middle
Калі працягваць мікрасэрвіс дапрацоўваць, тое можа атрымацца нешта накшталт вось гэтага
https://opexbot.info
Гэта мая распрацоўка, для тых каму лянота разбірацца, запускаць і лічыць самастойна. Планую дадаць туды аналітыку па просьбах тых, хто выкарыстоўвае. Калі вам спадабаўся артыкул, то падпісвайцеся на мой тэлеграм канал .
Полезная статья. Не могу представить, сколько усилий автора потребовалось, чтобы все описать. Благодарю.