diff --git a/.env b/.env index de1008f..1b3dece 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -TINKOFF_OPENAPI_URL=https://api-invest.tinkoff.ru/openapi +TINKOFF_OPENAPI_URL=https://invest-public-api.tbank.ru/rest/ TINKOFF_OPENAPI_TOKEN= diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index a9762d3..03e7f03 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -26,7 +26,7 @@ jobs: # uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1 with: - ruby-version: 3.1.0 + ruby-version: 3.4.5 - name: Install dependencies run: bundle install - name: Run rubocop diff --git a/.gitignore b/.gitignore index 9797a6c..00785ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env.local .env.*.local +.DS_Store diff --git a/.rubocop.yml b/.rubocop.yml index 44f5863..a74fa5f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,4 @@ -require: +plugins: - rubocop-performance AllCops: @@ -31,7 +31,7 @@ Style/Documentation: Enabled: false Layout/LineLength: - Max: 80 + Max: 100 Metrics/MethodLength: Max: 15 diff --git a/.ruby-version b/.ruby-version index fd2a018..4f5e697 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.1.0 +3.4.5 diff --git a/Dockerfile b/Dockerfile index 02f12f7..2a23ea5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.1.0 +FROM ruby:3.4.5 WORKDIR /opt/app COPY Gemfile* ./ diff --git a/Gemfile.lock b/Gemfile.lock index 0d18b34..011d76c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,15 @@ GEM remote: https://rubygems.org/ specs: - ast (2.4.2) + ast (2.4.3) awesome_print (1.9.2) awesome_pry (0.0.1) awesome_print pry-rails + bigdecimal (3.2.3) coderay (1.1.3) - dotenv (2.7.6) - faraday (0.17.4) + dotenv (3.1.8) + faraday (0.17.6) multipart-post (>= 1.2, < 3) faraday_middleware (0.14.0) faraday (>= 0.7.4, < 1.0) @@ -16,55 +17,66 @@ GEM faraday (~> 0.9) faraday_middleware (>= 0.9.1, < 1.0) oj (>= 2.0, < 4.0) - method_source (1.0.0) - multipart-post (2.1.1) - oj (3.13.10) - parallel (1.21.0) - parser (3.0.3.2) + json (2.13.2) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + method_source (1.1.0) + multipart-post (2.4.1) + oj (3.16.11) + bigdecimal (>= 3.0) + ostruct (>= 0.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) ast (~> 2.4.1) + racc pastel (0.8.0) tty-color (~> 0.5) - pry (0.14.1) + prism (1.4.0) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) - pry-rails (0.3.9) - pry (>= 0.10.4) - rainbow (3.0.0) - regexp_parser (2.2.0) - rexml (3.2.5) - rubocop (1.24.0) + pry-rails (0.3.11) + pry (>= 0.13.0) + racc (1.8.1) + rainbow (3.1.1) + regexp_parser (2.11.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.15.0, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.15.0) - parser (>= 3.0.1.1) - rubocop-performance (1.13.0) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - ruby-progressbar (1.11.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-performance (1.26.0) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + ruby-progressbar (1.13.0) strings (0.2.1) strings-ansi (~> 0.2) unicode-display_width (>= 1.5, < 3.0) unicode_utils (~> 1.4) strings-ansi (0.2.0) tty-color (0.6.0) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-table (0.12.0) pastel (~> 0.8) strings (~> 0.2.0) tty-screen (~> 0.8) - unicode-display_width (2.1.0) + unicode-display_width (2.6.0) unicode_utils (1.4.0) PLATFORMS - arm64-darwin-21 - x86_64-darwin-19 - x86_64-linux + arm64-darwin-24 + ruby DEPENDENCIES awesome_pry @@ -77,4 +89,4 @@ DEPENDENCIES tty-table BUNDLED WITH - 2.2.22 + 2.7.1 diff --git a/README.md b/README.md index 81d86ab..a6aef15 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,17 @@ # TinkyClient — tiny client for Tinkoff OpenAPI -Предлагаю вашему вниманию небольшой консольный Ruby-клиент для доступа к брокерскому аккаунту Тинькофф Инвестиции. -На данный момент это очень ранняя пре-альфа-версия, реализовано только отображение портфолио. -Цель проекта — сделать удобный консольный клиент для контроля своих активов, дополняющий официальное мобильное приложение [Инвестиции](https://www.tinkoff.ru/invest/). +Предлагаю вашему вниманию небольшой консольный Ruby-клиент для доступа к брокерскому аккаунту Т-Инвестиции. +На данный момент реализовано только отображение портфолио. +Цель проекта — сделать удобный консольный клиент для контроля своих активов, дополняющий официальное мобильное приложение [Т-Инвестиции](https://tbank.ru/invest/). ![Portfolio](/examples/portfolio.png) # Быстрый старт Требования: -- установленный Ruby 2.7.1 и новее -- наличие [токена Tinkoff OpenAPI](https://tinkoffcreditsystems.github.io/invest-openapi/auth/) +- установленный Ruby 3.4.5 и новее +- наличие [токена Tinkoff OpenAPI](https://developer.tbank.ru/invest/intro/intro/token) ```sh $ bundle @@ -34,15 +34,17 @@ $ bin/portfolio ### Колонки таблицы - `Type` — тип актива. - - `STOCK` — акции. + - `SHARE` — акции. - `BOND` — облигации. - `ETF` — инвестиционный фонд. - `Name` — название актива. - `Amount` — количество в штуках или сумма в валюте. - `Avg. buy` — средняя цена покупки актива. Показатель берётся напрямую из OpenAPI. Например: если вы купили 2 акции за 10 и 20 рублей, то средняя цена покупки будет 15 рублей. От этой стоимости и текущей цены считается ожидаемый доход. -- `Current price` — текущая цена актива. Не отдаётся напрямую из OpenAPI, поэтому программа вычисляет цену по формуле: `((balance * avg_buy_price) + expected_yield) / balance`. Возможно небольшое отличие от тикеров на сервере брокера. -- `Yield` — ожидаемый доход в валюте. Показатель берётся напрямую из OpenAPI. -- `Yield %` — ожидаемый доход в процентах. Не отдаётся напрямую из OpenAPI, поэтому программа вычисляет процент по формуле: `expected_yield / (avg_buy_price * balance) * 100`. +- `Current price` — текущая цена актива. Отдаётся напрямую из API. +- `Buy sum` — начальная стоимость актива по средней цене на момент покупки. Не отдаётся напрямую из API, поэтому программа вычисляет сумму по формуле: `avg_buy_price * amount`. +- `Current sum` — текущая стоимость актива по текущей рыночной цене. Не отдаётся напрямую из API, поэтому программа вычисляет сумму по формуле: `current_price * amount`. +- `Yield` — ожидаемый доход в валюте. Показатель берётся напрямую из API. +- `Yield %` — ожидаемый доход в процентах. Не отдаётся напрямую из API, поэтому программа вычисляет процент по формуле: `expected_yield / (avg_buy_price * amount) * 100`. ## Валюта @@ -67,8 +69,8 @@ $ bin/console ```sh pry(Tinky)> portfolio pry(Tinky)> wallet -pry(Tinky)> exchange_rates(positions) -pry(Tinky)> total_amount(positions) +pry(Tinky)> available_currencies +pry(Tinky)> puts summary_table(summary_data.values) ``` ## Запуск в Docker @@ -95,7 +97,7 @@ pry(Tinky)> total_amount(positions) Если вы хотите вывести портфолио в отдельное окно, чтобы оно при этом автоматически обновлялось, попробуйте команду: ```sh -$ watch bin/portfolio +$ bin/portfolio --watch ``` В зависимости от системы, `watch` надо устанавливать отдельно. Однако, на macOS вывод работает некорректно. Я устанавливал через `brew install watch`. Оказалось, что она некорректно показывает символы валют и убирает цвет. Пользователи также сообщали, что табличная вёрстка ломается. @@ -110,7 +112,7 @@ $ while sleep 2; do bin/portfolio > /tmp/portfolio; clear; cat /tmp/portfolio; d 1. Используя этот проект, никакие персональные данные НЕ ПЕРЕДАЮТСЯ никаким третьим лицам скрыто или явно. 2. Использование этого проекта не требует от пользователя никаких логинов, паролей, номеров телефона и других персональных данных. -3. Для доступа к вашем брокерскому счёту вы используете только ваш персональный токен из личного кабинета Тинькофф Инвестиций. +3. Для доступа к вашем брокерскому счёту вы используете только ваш персональный токен из личного кабинета Т-Инвестиций. 4. Этот токен вы генерируете самостоятельно. 5. Для нормальной работы этой программы вы самостоятельно записываете токен в текстовый файл, который сохраняется только на вашем устройстве. 6. Вы можете в любой момент отозвать (деактивировать) свой токен, если у вас возникнут подозрения в компрометации. @@ -125,8 +127,7 @@ MIT License. Используйте как хотите и где хотите # Ссылки -- https://www.tinkoff.ru/invest/ -- https://www.tinkoff.ru/invest/web-terminal/ -- https://github.com/TinkoffCreditSystems/invest-openapi/ -- https://tinkoffcreditsystems.github.io/invest-openapi/ +- https://tbank.ru/invest/ +- https://developer.tbank.ru/invest/api/t-invest-api +- https://github.com/RussianInvestments/investAPI - https://t.me/tinkoffinvestopenapi diff --git a/config/dotenv.rb b/config/dotenv.rb index 8bc88ee..e2f9bbb 100644 --- a/config/dotenv.rb +++ b/config/dotenv.rb @@ -1,7 +1,2 @@ require 'dotenv' -Dotenv.load( - ".env.#{ENV['APP_ENV']}.local", - '.env.local', - ".env.#{ENV['APP_ENV']}", - '.env' -) +Dotenv.load('.env.local', '.env') diff --git a/examples/wallet.png b/examples/wallet.png index 4eef95f..d52fa5f 100644 Binary files a/examples/wallet.png and b/examples/wallet.png differ diff --git a/lib/tinky.rb b/lib/tinky.rb index 193d3a2..380185f 100644 --- a/lib/tinky.rb +++ b/lib/tinky.rb @@ -1,35 +1,36 @@ require './config/dotenv' require './config/oj' - require 'bigdecimal/util' - require 'tty/table' - require 'pry' require 'awesome_print' - require './lib/tinky/client' require './lib/tinky/client_error' module Tinky # rubocop:disable Metrics/ModuleLength CURRENCIES = { - RUB: { symbol: '₽', ticker: nil }, - USD: { symbol: '$', ticker: 'USD000UTSTOM' }, - EUR: { symbol: '€', ticker: 'EUR_RUB__TOM' } + rub: { symbol: '₽', ticker: 'RUB000UTSTOM' }, + usd: { symbol: '$', ticker: 'USD000UTSTOM' }, + eur: { symbol: '€', ticker: 'EUR_RUB__TOM' }, + cny: { symbol: '¥', ticker: 'CNYRUB_TOM_CETS' }, + try: { symbol: '₺', ticker: 'TRYRUB_TOM_CETS' } }.freeze - class << self - def portfolio - items = positions + CURRENCY_MODE = :rub # NOTE: for further development + class << self # rubocop:disable Metrics/ClassLength + def portfolio puts "\nPortfolio:" - puts portfolio_table(items) + puts portfolio_table(positions) puts "\nTotal amount summary:" + puts summary_table(summary_data.values) - rates = exchange_rates(items) - summary_data = full_summary(items, rates).values - puts summary_table(summary_data) + puts "\nUser info:" + puts user_info_table + + puts "\nAccount info:" + puts account_table print_timestamp end @@ -38,19 +39,14 @@ def watch_portfolio print `tput smcup` loop do - items = positions - print `clear` puts 'Portfolio:' - puts portfolio_table(items) + puts portfolio_table(positions) puts puts 'Total amount summary:' - - rates = exchange_rates(items) - summary_data = full_summary(items, rates).values - puts summary_table(summary_data) + puts summary_table(summary_data.values) print_timestamp @@ -63,10 +59,8 @@ def restore_tty end def wallet - items = client.portfolio_currencies.dig(:payload, :currencies) - puts "\nWallet:" - puts wallet_table(items) + puts wallet_table(currency_positions) print_timestamp end @@ -75,7 +69,7 @@ def portfolio_table(items) prev_type = items.first[:instrumentType] table = TTY::Table.new( - header: ['Type', 'Name', 'Amount', 'Avg. buy', 'Current price', + header: ['Type', 'Name', 'Amount', 'Avg. buy', 'Current price', 'Buy sum', 'Current sum', 'Yield', 'Yield %'] ) @@ -92,8 +86,9 @@ def wallet_table(items) table = TTY::Table.new(header: %w[Currencies]) items.each do |item| - currency = CURRENCIES[item[:currency].to_sym] - formatted_value = format('%.2f %s', item[:balance], currency[:symbol]) + currency_symbol = symbol_by_ticker(item[:ticker]) + value = decorate_price(item[:quantity])[0] + formatted_value = format('%.2f %s', value, currency_symbol) table << [ { @@ -106,67 +101,42 @@ def wallet_table(items) table.render(:unicode, padding: [0, 1, 0, 1]) end - def total_amount(positions) # rubocop:disable Metrics/AbcSize - total = Hash.new do |h, k| - h[k] = { price: 0, yield: 0, total: 0 } - end - - positions.each_with_object(total) do |item, result| - currency = item.dig(:averagePositionPrice, :currency).to_sym - avg_price = item.dig(:averagePositionPrice, :value).to_d - price = avg_price * item[:balance].to_d - expected_yield = item.dig(:expectedYield, :value).to_d - - result[currency][:price] += price - result[currency][:yield] += expected_yield - result[currency][:total] += price + expected_yield + def user_info_table + table = TTY::Table.new + user_info_rows.each do |row| + table << row end + table.render(:unicode, padding: [0, 1, 0, 1]) end - def exchange_rates(positions) - # calculate exchange rate in RUB by currency - currencies(positions).reduce({}) do |result, c| - last_currency_candle = client.market_candles(candles_params(c[:figi])) - rate = last_currency_candle.dig(:payload, :candles).last[:c] - - # get currency code by ticker - currency = currency_by_ticker(c[:ticker]) - - result.merge(currency => rate) + def account_table + table = TTY::Table.new + account_rows.each do |row| + table << row end + table.render(:unicode, padding: [0, 1, 0, 1]) end - def candles_params(figi) - current_time = Time.now + def summary_data + expected_yield = total_without_currencies / (100 + total_yield[0]) * total_yield[0] + symbol = CURRENCIES[CURRENCY_MODE][:symbol] { - figi:, - from: (current_time - (24 * 3600 * 3)).iso8601, - to: current_time.iso8601, - interval: 'hour' + total_purchases: [total_purchases, symbol], + expected_yield: [expected_yield, symbol], + expected_total: [total_without_currencies, symbol], + total_yield: total_yield, + rub_balance: rub_balance, + total_with_rub: decorate_price(portfolio_data[:totalAmountPortfolio]) } end - def summary(items, rates) # rubocop:disable Metrics/AbcSize - total = Hash.new { |h, k| h[k] = [0, '₽'] } - - total_amount(items).each_with_object(total) do |(key, value), memo| - rate = rates.fetch(key, 1) - - memo[:price][0] += value[:price] * rate - memo[:yield][0] += value[:yield] * rate - memo[:total][0] += value[:total] * rate + def available_currencies + @available_currencies ||= client.currencies[:instruments].reduce({}) do |memo, currency| + data = currency.slice(:ticker, :name) + memo.merge!(currency[:isoCurrencyName].to_sym => data) end end - def full_summary(items, rates) - result = summary(items, rates) - - result.merge( - yield_percent: [result[:yield][0] / result[:price][0] * 100, '%'], - total_with_rub: [result[:total][0] + rub_balance, '₽'] - ) - end - private def client Client.new @@ -177,52 +147,63 @@ def pastel end def portfolio_data - client.portfolio + @portfolio_data ||= client.portfolio(currency_mode: CURRENCY_MODE) end - def sell_price(item) - balance = item[:balance].to_d - avg_buy_price = item[:averagePositionPrice][:value].to_d - expected_yield = item[:expectedYield][:value].to_d + def user_data + @user_data ||= client.user_info + end - { - value: ((balance * avg_buy_price) + expected_yield) / balance, - currency: item[:averagePositionPrice][:currency] - } + def account_data + @account_data ||= client.accounts[:accounts].first end - def row_data(item) + def row_data(item) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + currency = item[:averagePositionPrice][:currency] + amount = decorate_amount(item[:quantity][:units]).to_i + avg_buy_price = decorate_price(item[:averagePositionPrice]) + current_price = decorate_price(item[:currentPrice]) + buy_sum = [(avg_buy_price[0] * amount).round(2), avg_buy_price[1]] + current_sum = [(current_price[0] * amount).round(2), current_price[1]] + [ item[:instrumentType].upcase, - decorate_name(item[:name]), - { value: decorate_amount(item[:balance]), alignment: :right }, - { - value: decorate_price(item[:averagePositionPrice]), - alignment: :right - }, - { - value: decorate_price(sell_price(item)), - alignment: :right - }, - { value: decorate_yield(item[:expectedYield]), alignment: :right }, + decorate_name(item[:ticker]), + { value: amount, alignment: :right }, + { value: avg_buy_price.join(' '), alignment: :right }, + { value: current_price.join(' '), alignment: :right }, + { value: buy_sum.join(' '), alignment: :right }, + { value: current_sum.join(' '), alignment: :right }, + { value: decorate_yield(item[:expectedYield], currency), alignment: :right }, { value: decorate_yield_percent(item), alignment: :right } ] end - def decorate_yield(expected_yield) - value = expected_yield[:value] - currency = CURRENCIES[expected_yield[:currency].to_sym] + def decorate_yield(expected_yield, currency = 'usd') + value = expected_yield[:units].to_d + (expected_yield[:nano].to_d / (10**9)) + currency = CURRENCIES[currency.to_sym] - formatted_value = format('%+.2f %s', value, currency[:symbol]) + formatted_value = format('%+.2f %s', value.round(2), currency[:symbol]) pastel.decorate(formatted_value, yield_color(value)) end + def zero_item?(item) + item[:averagePositionPrice][:units].to_d.zero? || item[:quantity][:units].to_d.zero? + end + + def total_buy(item) + decorate_price(item[:averagePositionPrice])[0] * item[:quantity][:units].to_d + end + def decorate_yield_percent(item) - total = item.dig(:averagePositionPrice, :value).to_d * item[:balance].to_d - value = item.dig(:expectedYield, :value).to_d / total.to_d * 100 + yield_percent = if zero_item?(item) + 0.0 + else + decorate_price(item[:expectedYield])[0].to_d / total_buy(item).to_d * 100 + end - formatted_value = format('%+.2f %%', value.round(2)) - pastel.decorate(formatted_value, yield_color(value)) + formatted_value = format('%+.2f %%', yield_percent.round(2)) + pastel.decorate(formatted_value, yield_color(yield_percent)) end def yield_color(value) @@ -254,27 +235,21 @@ def decorate_name(name) end def decorate_price(price) - currency = CURRENCIES[price[:currency].to_sym] - format('%.2f %s', price[:value], currency[:symbol]) + value = price[:units].to_d + (price[:nano].to_d / (10**9)) + currency_symbol = price.key?(:currency) ? CURRENCIES[price[:currency].to_sym][:symbol] : '%' + [value.to_f.round(2), currency_symbol] end def print_timestamp puts "\nLast updated: #{Time.now}\n\n" end - def currency_by_ticker(ticker) - CURRENCIES.select { |_, v| v[:ticker] == ticker }.keys.first - end - def positions - portfolio_data.dig(:payload, :positions) + portfolio_data[:positions] end - # select only currencies positions (wallet) - def currencies(positions) - positions.select do |position| - position[:instrumentType] == 'Currency' - end + def currency_positions + portfolio_data[:positions].select { |i| i[:instrumentType] == 'currency' } end def decorate_summary(items) @@ -294,6 +269,7 @@ def summary_table(items) 'Expected Yield', 'Expected Total', 'Yield %', + 'RUB balance', 'Total + RUB balance' ] ) @@ -301,11 +277,46 @@ def summary_table(items) table.render(:unicode, padding: [0, 1, 0, 1]) end + def symbol_by_ticker(ticker) + pair = CURRENCIES.values.find { |c| c[:ticker] == ticker.to_s } + pair&.fetch(:symbol, nil) || '?' + end + + def total_yield + @total_yield ||= decorate_price(portfolio_data[:expectedYield]) + end + + def total_purchases + total_without_currencies / (100 + total_yield[0]) * 100 + end + + def total_without_currencies + @total_without_currencies ||= + decorate_price(portfolio_data[:totalAmountPortfolio])[0] - rub_balance[0] + end + def rub_balance - client - .portfolio_currencies - .dig(:payload, :currencies) - .find { |i| i[:currency] == 'RUB' }[:balance].to_d + decorate_price(portfolio_data[:totalAmountCurrencies]) + end + + def user_info_rows # rubocop:disable Metrics/AbcSize + [ + [pastel.bold('User ID'), user_data[:userId]], + [pastel.bold('Premium status'), user_data[:premStatus] ? '✅' : '❌'], + [pastel.bold('Qual status'), user_data[:qualStatus] ? '✅' : '❌'], + [pastel.bold('Tariff'), user_data[:tariff].capitalize], + [pastel.bold('Risk level'), user_data[:riskLevelCode].capitalize] + ] + end + + def account_rows # rubocop:disable Metrics/AbcSize + [ + [pastel.bold('Account ID'), account_data[:id]], + [pastel.bold('Type'), account_data[:type]], + [pastel.bold('Name'), account_data[:name]], + [pastel.bold('Opened at'), Time.parse(account_data[:openedDate]).strftime('%d %b %Y')], + [pastel.bold('Access level'), account_data[:accessLevel]] + ] end end end diff --git a/lib/tinky/client.rb b/lib/tinky/client.rb index 58102bf..14bd1a1 100644 --- a/lib/tinky/client.rb +++ b/lib/tinky/client.rb @@ -4,27 +4,36 @@ module Tinky class Client + NAMESPACE = 'tinkoff.public.invest.api.contract.v1'.freeze attr_reader :connection def initialize - @connection = Client.make_connection(ENV['TINKOFF_OPENAPI_URL']) + @connection = Client.make_connection(ENV.fetch('TINKOFF_OPENAPI_URL', nil)) end - def portfolio - get_data('portfolio') + def portfolio(currency_mode:) + account_id = accounts[:accounts].first[:id] + request_data('OperationsService/GetPortfolio', + { accountId: account_id, currency: currency_mode }) end - def portfolio_currencies - get_data('portfolio/currencies') + def currencies + request_data('InstrumentsService/Currencies', + { instrumentStatus: 'INSTRUMENT_STATUS_UNSPECIFIED', + instrumentExchange: 'INSTRUMENT_EXCHANGE_UNSPECIFIED' }) end - def market_candles(params = {}) - get_data('market/candles', params) + def user_info + request_data('UsersService/GetInfo') + end + + def accounts + request_data('UsersService/GetAccounts', { status: 'ACCOUNT_STATUS_OPEN' }) end private - def get_data(url, params = {}) - request(:get, url, params) + def request_data(url, params = {}) + request(:post, [NAMESPACE, url].join('.'), params) end def request(method, url, params = {}) @@ -46,9 +55,9 @@ def handle_error(response) class << self def make_connection(url) - Faraday.new(url:) do |builder| + Faraday.new(url:, ssl: { verify: false }) do |builder| builder.request :json - builder.authorization :Bearer, ENV['TINKOFF_OPENAPI_TOKEN'] + builder.authorization :Bearer, ENV.fetch('TINKOFF_OPENAPI_TOKEN', nil) builder.response :oj, content_type: 'application/json' builder.adapter Faraday.default_adapter end