GithubHelp home page GithubHelp logo

invest-api-go-sdk's Introduction

INVEST API Go SDK

Go Reference

SDK предназначен для упрощения работы с API Инвестиций.

Начало работы

go get github.com/russianinvestments/invest-api-go-sdk

Где взять токен аутентификации?

В разделе инвестиций вашего личного кабинета tinkoff . Далее:

  • Перейдите в настройки
  • Проверьте, что функция “Подтверждение сделок кодом” отключена
  • Выпустите токен (если не хотите через API выдавать торговые поручения, то надо выпустить токен "только для чтения")
  • Скопируйте токен и сохраните, токен отображается только один раз, просмотреть его позже не получится, тем не менее вы можете выпускать неограниченное количество токенов.

Документация

Документацию непосредственно по INVEST API можно найти по ссылке.

Быстрый старт

Для непосредственного взаимодействия с INVEST API нужно создать клиента. Примеры использования SDK находятся в директории examples.

Запуск примеров

1. Клонирование репозитория

git clone https://github.com/russianinvestments/invest-api-go-sdk

2. Конфигурация SDK

Перейдите в директорию с примерами

cd invest-api-go-sdk/examples

Создайте файл config.yaml

touch "config.yaml"

И заполните его по примеру example.yaml

AccountId: ""
APIToken: <your_token>
EndPoint: sandbox-invest-public-api.tinkoff.ru:443
AppName: invest-api-go-sdk
DisableResourceExhaustedRetry: false
DisableAllRetry: false
MaxRetries: 3

Для быстрого старта на песочнице достаточно указать только токен, остальное заполнится по умолчанию.

Так же вы можете не использовать .yaml файлы, а в main функции вместо investgo.LoadConfig() явно создать investgo.Config, и заполнить его по описанию:

type Config struct {
// EndPoint - Для работы с реальным контуром и контуром песочницы нужны разные эндпоинты.
// По умолчанию = sandbox-invest-public-api.tinkoff.ru:443
// https://tinkoff.github.io/investAPI/url_difference/
EndPoint string `yaml:"EndPoint"`
// Token - Ваш токен для InvestAPI
Token string `yaml:"APIToken"`
// AppName - Название вашего приложения, по умолчанию = tinkoff-api-go-sdk
AppName string `yaml:"AppName"`
// AccountId - Если уже есть аккаунт для апи можно указать напрямую,
// по умолчанию откроется новый счет в песочнице
AccountId string `yaml:"AccountId"`
// DisableResourceExhaustedRetry - Если true, то сдк не пытается ретраить, после получения ошибки об исчерпывании
// лимита запросов, если false, то сдк ждет нужное время и пытается выполнить запрос снова. По умолчанию = false
DisableResourceExhaustedRetry bool `yaml:"DisableResourceExhaustedRetry"`
// DisableAllRetry - Отключение всех ретраев
DisableAllRetry bool `yaml:"DisableAllRetry"`
// MaxRetries - Максимальное количество попыток переподключения, по умолчанию = 3
// (если указать значение 0 это не отключит ретраи, для отключения нужно прописать DisableAllRetry = true)
MaxRetries uint `yaml:"MaxRetries"`
}

3. Запуск

Важно! В примерах роботов interval_bot и загрузчика стаканов order_book_download используется драйвер для sqlite, который является cgo пакетом. Перед запуском убедитесь, что на вашем компьютере установлен gcc.

Пример использования MarketDataStreamService:

go run md_stream.go

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

go run order_book_download/order_book.go

Дополнительные возможности

  • Загрузка исторических данных. В рамках сервиса Marketdata, метод GetHistoricCandles возвращает список свечей в интервале (from - to), метод GetAllHistoricCandles возвращает все доступные свечи.
  • Получение метеданных. В теле ответа Unary - методов присутствует grpc.Header, при момощи методов investgo.MessageFromHeader и investgo.RemainingLimitFromHeader вы можете получить сообщение ошибки, и текущий остаток запросов соответсвенно. Подробнее про заголовки тут
  • Переподключение. По умолчанию включен ретраер, который при получении ошибок от grpc пытается выполнить запрос повторно, а в случае со стримами переподклчается и переподписывает стрим на всю подписки. Отдельно можно отключить ретраер для ошибки ResourceExhausted, по умолчанию он включен и в случае превышения лимитов Unary - запросов, ретраер ждет нужное время и продолжает выполнение, при этом никакого сообщения об ошибке для клиента нет.
Пример использования MarketDataStreamService
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"sync"
	"syscall"

	"github.com/russianinvestments/invest-api-go-sdk/investgo"
	pb "github.com/russianinvestments/invest-api-go-sdk/proto"
	"go.uber.org/zap"
)

func main() {
	// инициализация... //

	// создаем клиента для апи инвестиций, он поддерживает grpc соединение
	client, err := investgo.NewClient(ctx, config, logger)
	if err != nil {
		logger.Errorf("Client creating error %v", err.Error())
	}
	defer func() {
		logger.Infof("Closing client connection")
		err := client.Stop()
		if err != nil {
			logger.Errorf("client shutdown error %v", err.Error())
		}
	}()

	// для синхронизации всех горутин
	wg := &sync.WaitGroup{}

	// один раз создаем клиента для стримов
	MDClient := client.NewMarketDataStreamClient()

	// создаем стримов сколько нужно, например 2
	firstMDStream, err := MDClient.MarketDataStream()
	if err != nil {
		logger.Errorf(err.Error())
	}
	// результат подписки на инструменты это канал с определенным типом информации, при повторном вызове функции
	// подписки(например на свечи), возвращаемый канал можно игнорировать, так как при первом вызове он уже был получен
	firstInstrumetsGroup := []string{"BBG004730N88", "BBG00475KKY8", "BBG004RVFCY3"}
	candleChan, err := firstMDStream.SubscribeCandle(firstInstrumetsGroup, pb.SubscriptionInterval_SUBSCRIPTION_INTERVAL_ONE_MINUTE, true)
	if err != nil {
		logger.Errorf(err.Error())
	}

	tradesChan, err := firstMDStream.SubscribeTrade(firstInstrumetsGroup)
	if err != nil {
		logger.Errorf(err.Error())
	}

	// функцию Listen нужно вызвать один раз для каждого стрима и в отдельной горутине
	// для останвки стрима можно использовать метод Stop, он отменяет контекст внутри стрима
	// после вызова Stop закрываются каналы и завершается функция Listen
	wg.Add(1)
	go func() {
		defer wg.Done()
		err := firstMDStream.Listen()
		if err != nil {
			logger.Errorf(err.Error())
		}
	}()

	// для дальнейшей обработки, поступившей из канала, информации хорошо подойдет механизм,
	// основанный на паттерне pipeline https://go.dev/blog/pipelines

	wg.Add(1)
	go func(ctx context.Context) {
		defer wg.Done()
		for {
			select {
			case <-ctx.Done():
				logger.Infof("Stop listening first channels")
				return
			case candle, ok := <-candleChan:
				if !ok {
					return
				}
				// клиентская логика обработки...
				fmt.Println("high price = ", candle.GetHigh().ToFloat())
			case trade, ok := <-tradesChan:
				if !ok {
					return
				}
				// клиентская логика обработки...
				fmt.Println("trade price = ", trade.GetPrice().ToFloat())
			}
		}
	}(ctx)
    
	// полный пример - examples/md_stream.go // 
}

У меня есть вопрос

Основной репозиторий с документацией — в нем вы можете задать вопрос в Issues и получать информацию о релизах в Releases. Если возникают вопросы по данному SDK, нашёлся баг или есть предложения по улучшению, то можно задать его в Issues этого репозитория.

invest-api-go-sdk's People

Contributors

jstalex avatar poliagos avatar vlanse avatar

Stargazers

Егор Суханов avatar Denis Sirotkin avatar  avatar Anton Nagorniy avatar Stepanov Denis avatar Gorelykh Anatoly avatar Max Kuznetsov avatar denis avatar  avatar Igor Baskakov avatar Aik avatar Vladislav Molotsilo avatar Antares avatar Nikolay Ishmametyev avatar Ilya Orazov avatar Sergey Gorbatenko avatar Eric Vlaskin avatar hhullen avatar Sergius Novikov avatar Evgeny Svet avatar Vladislav Petrochenko avatar Dima Vdovin avatar  avatar Viktor Solovev avatar Pavel Zloi avatar  avatar  avatar Pavel avatar Oleksii avatar  avatar Maxim avatar Nikolay Bayborodin avatar Evgeny Markov avatar Mark Krymov avatar Stanislav avatar Evgeny Samsonov avatar  avatar  avatar  avatar

Watchers

Ilya Brin avatar Andrey Tkachenko avatar

invest-api-go-sdk's Issues

Невозможно отписаться от стаканов с разными глубинами

У вас можно подписаться на стаканы разной глубины, но при отписке глубину нельзя указать, в итоге я не могу отписаться

func (mds *MarketDataStream) UnSubscribeOrderBook(ids []string) error {
err := mds.sendOrderBookReq(ids, 0, pb.SubscriptionAction_SUBSCRIPTION_ACTION_UNSUBSCRIBE)
if err != nil {
return err
}
for _, id := range ids {
delete(mds.subs.orderBooks, id)
}
return nil
}

Некорректное поведение NewClient

if conf.AccountId == "" {

Метод открывает соединение, но неожиданно оказывается при открытии соединения у него есть и side-effect, открывет счет в песочнице. Не надо так. Этот метод должен только открывать соединение и все.

И никакой AccountId в конфиге не нужен. У пользователя 1+ аккаунтов.

Невозможно передать парметры TrailingStop ордера

Невозможно передать парметры TrailingStop ордера в StopOrdersServiceClient.PostStopOrder). Метод принимает ссылку структуру, в которой отсутствует поле с необходимым параметром, хотя в сгенерированных proto файлах такой параметр есть

Обработка завершения подписки

Добрый день!

Есть проблема с обработкой завершения подписки
{"level":"info","time":"2023-09-22T16:41:01.283204879+03:00","caller":"investgo/md_stream.go:265","msg":"stop listening market data stream"}
{"level":"info","time":"2023-09-22T16:41:01.283307992+03:00","caller":"investgo/md_stream.go:296","msg":"close market data stream"}

Завершился marketdata stream, но торги еще идут. Можете ли добавить обработку завершения стрима в md_stream.go? Очень неудобно такое отлавливать без правок в SDK - где стрим создается.

Спасибо!

Клиент поломан (v1.24.0)

Для некоторых вызовов (напр Shares с параметром InstrumentStatus_INSTRUMENT_STATUS_ALL), где отдается большой объем данных в результате, штатное использование клиента невозможно, поскольку нельзя "нормальным" образом указать этот самый предельный размер ответа (grpc.MaxCallRecvMsgSize)

Костыль для обхода заключается в том, чтобы создать соединение самостоятельно и с помощью него напрямую использовать сгенеренный код, указав соответствующую опцию

P.S. Если честно, наличие корявых оберток вокруг сгенеренных вызовов в любом случае плохо объяснимо, например в текущем клиенте не удается нормально переиспользовать клиента и пробрасывать контекст для каждого вызова - контекст зачем-то закапывается внутрь клиента при создании. Т.о. если хочешь передавать правильный контекст каждый раз, надо пересоздавать клиента (и соединение). Либо же создать его один раз, и переиспользовать - но забыть про правильный контекст.

Неточность таймера

Таймер ожидает до времени срабатывания событий START и STOP используя отсчет по системным часам, которые неизбежно имеют некоторую неточность. Если в процессе ожидания выполнится синхронизация системного времени с NTP-сервером, то окончится окончание (по локальным часам) уже не точно в заданное время, а немного раньше или позже. В примере ниже событие STOP пришло на несколько миллисекунд раньше, чем нужно, в результате тут же снова незапланированно сработало START (cancelAhead = 1 минута).

2024-04-15T09:59:59.559+0300	INFO	timer	start trading session, remaining time = 8h39m59.4400182s
2024-04-15T09:59:59.559+0300	DEBUG	buy	got event = 1 (investgo.START)
2024-04-15T09:59:59.561+0300	INFO	buy	MOEX trade session started!
...
2024-04-15T18:38:58.681+0300	DEBUG	buy	got event = 2 (investgo.STOP)
2024-04-15T18:38:58.681+0300	INFO	buy	MOEX trade session ends
...
2024-04-15T18:38:58.714+0300	INFO	timer	start trading session, remaining time = 1m0.2852115s
2024-04-15T18:38:58.714+0300	DEBUG	buy	got event = 1 (investgo.START)
2024-04-15T18:38:58.715+0300	INFO	buy	MOEX trade session started!

Пулл реквест для исправления данного бага
#15

 Отсутствие обработки состояния подписки в marketdatastream

func (mds *MarketDataStream) sendRespToChannel(resp *pb.MarketDataResponse) {
switch resp.GetPayload().(type) {
case *pb.MarketDataResponse_Candle:
mds.candle <- resp.GetCandle()
case *pb.MarketDataResponse_Orderbook:
mds.orderBook <- resp.GetOrderbook()
case *pb.MarketDataResponse_Trade:
mds.trade <- resp.GetTrade()
case *pb.MarketDataResponse_LastPrice:
mds.lastPrice <- resp.GetLastPrice()
case *pb.MarketDataResponse_TradingStatus:
mds.tradingStatus <- resp.GetTradingStatus()
default:
mds.mdsClient.logger.Infof("info from MD stream %v", resp.String())
}
}

Планируется ли обработка состояния подписки pb.MarketDataResponse_SubscribeCandlesResponse и других?
Если нет, почему вы их игнорируете?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.