-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from Tinkoff/dev
add order book bot example
- Loading branch information
Showing
15 changed files
with
1,218 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
## Examples | ||
|
||
В примерах рассмотрены основные сценарии использования investAPI через пакет investgo. | ||
|
||
* `md_stream.go`, `orders_stream.go`, `operations_stream.go` - примеры работы со стримами | ||
* `instruments.go` - примеры работы с сервисом инструментов | ||
* `marketdata.go` - примеры работы с сервисом котировок | ||
* `operations.go` - примеры работы с сервисом операций | ||
* `orders.go` - примеры работы с сервисом торговых поручений | ||
* `stop_orders` - примеры работы с сервисом стоп-заявок | ||
* `users.go` - примеры работы с сервисом счетов | ||
* `sandbox.go` - пример работы с песочницей | ||
* `order_book_download/order_book.go` - пример сохранения стаканов из стрима маркетдаты в sqlite или json | ||
* `ob_bot` - пример простейшего бота на стакане |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
## Робот на стакане | ||
|
||
### Стратегия | ||
Робот отслеживает "стакан". Если лотов в заявках на покупку больше, чем в лотах на продажу в `BuyRatio` раз, | ||
то поступает сигнал на покупку, в противном случае, если лотов в заявках на продажу больше, чем в лотах на покупку | ||
в `SellRatio` раз - поступает сигнал на продажу | ||
|
||
#### Конфигурация | ||
```go | ||
type OrderBookStrategyConfig struct { | ||
// Instruments - слайс идентификаторов инструментов | ||
Instruments []string | ||
// Depth - Глубина стакана | ||
Depth int32 | ||
// Если кол-во бид/аск больше чем BuyRatio - покупаем | ||
BuyRatio float64 | ||
// Если кол-во аск/бид больше чем SellRatio - продаем | ||
SellRatio float64 | ||
// MinProfit - Минимальный процент выгоды, с которым можно совершать сделки | ||
MinProfit float64 | ||
// SellOut - Если true, то по достижению дедлайна бот выходит из всех активных позиций | ||
SellOut bool | ||
} | ||
``` | ||
|
||
### Исполнитель | ||
Под стратегию написан простейший исполнитель, который выставляет рыночные поручения. | ||
Пока реализована возможность открывать только long позиции. | ||
|
||
**Покупка** | ||
|
||
Заявка на покупку *не* выставляется если: | ||
* Позиция уже открыта | ||
* На счету недостаточно денежных средств | ||
|
||
**Продажа** | ||
|
||
Заявка на продажу *не* выставляется если: | ||
* Позиция не открыта | ||
* Цена открытия позиции меньше цены последней сделки по этому инструменту | ||
|
||
### Режим работы | ||
Данный пример ориентирован на торговлю внутри одного дня. За расписанием торгов следит `investgo.Timer`, | ||
он сигнализирует о начале и завершении основной торговй сессии на сегодня. | ||
При запуске main `investgo.Timer` возвращает канал с событиями, START/STOP - сигналы к запуску и остановке бота, | ||
если выставлен флаг `SellOut` в конфигурации стратеги и время `cancelAhead` при создании таймера, то бот завершит работу и закроет все | ||
позиции за `cancelAhead` до конца торгов текущего дня. | ||
|
||
### Запуск | ||
|
||
$ git clone https://github.com/tinkoff/invest-api-go-sdk | ||
|
||
$ cd invest-api-go-sdk/examples/ob_bot | ||
|
||
Создайте файл `config.yaml` | ||
|
||
$ touch "config.yaml" | ||
|
||
И заполните его по примеру `example.yaml` | ||
|
||
```yaml | ||
AccountId: "" | ||
APIToken: <your_token> | ||
EndPoint: sandbox-invest-public-api.tinkoff.ru:443 | ||
AppName: invest-api-go-sdk | ||
DisableResourceExhaustedRetry: false | ||
DisableAllRetry: false | ||
MaxRetries: 3 | ||
``` | ||
*Для быстрого старта на песочнице достаточно указать только токен, остальное заполнится по умолчанию.* | ||
$ go run cmd/main.go | ||
Обратите внимание, что в одной функции main есть возможность создать несколько клиентов для investAPI c разными | ||
токенами и счетами, а с разными клиентами можно создавать разных ботов и запускать их одновременно. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"github.com/tinkoff/invest-api-go-sdk/examples/ob_bot/internal/bot" | ||
"github.com/tinkoff/invest-api-go-sdk/investgo" | ||
pb "github.com/tinkoff/invest-api-go-sdk/proto" | ||
"go.uber.org/zap" | ||
"log" | ||
"os" | ||
"os/signal" | ||
"sync" | ||
"syscall" | ||
"time" | ||
) | ||
|
||
const ( | ||
// SHARES_NUM - Количество акций для торгов | ||
SHARES_NUM = 30 | ||
// EXCHANGE - Биржа на которой будет работать бот | ||
EXCHANGE = "MOEX" | ||
) | ||
|
||
func main() { | ||
// загружаем конфигурацию для сдк из .yaml файла | ||
sdkConfig, err := investgo.LoadConfig("config.yaml") | ||
if err != nil { | ||
log.Fatalf("config loading error %v", err.Error()) | ||
} | ||
|
||
sigs := make(chan os.Signal, 1) | ||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
// сдк использует для внутреннего логирования investgo.Logger | ||
// для примера передадим uber.zap | ||
prod := zap.NewExample() | ||
defer func() { | ||
err := prod.Sync() | ||
if err != nil { | ||
log.Printf("Prod.Sync %v", err.Error()) | ||
} | ||
}() | ||
if err != nil { | ||
log.Fatalf("logger creating error %v", err) | ||
} | ||
logger := prod.Sugar() | ||
// создаем клиента для investAPI, он позволяет создавать нужные сервисы и уже | ||
// через них вызывать нужные методы | ||
client, err := investgo.NewClient(ctx, sdkConfig, logger) | ||
if err != nil { | ||
logger.Fatalf("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()) | ||
} | ||
}() | ||
|
||
// для создания стратеги нужно ее сконфигурировать, для этого получим список идентификаторов инструментов, | ||
// которыми предстоит торговать | ||
insrtumentsService := client.NewInstrumentsServiceClient() | ||
// получаем список акций доступных для торговли через investAPI | ||
instrumentsResp, err := insrtumentsService.Shares(pb.InstrumentStatus_INSTRUMENT_STATUS_BASE) | ||
if err != nil { | ||
logger.Errorf(err.Error()) | ||
} | ||
// слайс идентификаторов торговых инструментов instrument_uid | ||
// акции с московской биржи | ||
instrumentIds := make([]string, 0, 300) | ||
shares := instrumentsResp.GetInstruments() | ||
for _, share := range shares { | ||
if len(instrumentIds) > SHARES_NUM-1 { | ||
break | ||
} | ||
if share.GetExchange() == EXCHANGE { | ||
instrumentIds = append(instrumentIds, share.GetUid()) | ||
} | ||
} | ||
logger.Infof("got %v instruments\n", len(instrumentIds)) | ||
|
||
instruments := instrumentIds | ||
// instruments := []string{"6afa6f80-03a7-4d83-9cf0-c19d7d021f76", "e6123145-9665-43e0-8413-cd61b8aa9b13"} | ||
|
||
// конфиг стратегии бота на стакане | ||
orderBookConfig := bot.OrderBookStrategyConfig{ | ||
Instruments: instruments, | ||
Depth: 20, | ||
BuyRatio: 2, | ||
SellRatio: 2, | ||
MinProfit: 0.5, | ||
SellOut: true, | ||
} | ||
|
||
// создание бота на стакане | ||
botOnOrderBook, err := bot.NewBot(ctx, client, orderBookConfig) | ||
if err != nil { | ||
logger.Fatalf("bot on order book creating fail %v", err.Error()) | ||
} | ||
|
||
wg := &sync.WaitGroup{} | ||
// Таймер для Московской биржи, отслеживает расписание и дает сигналы, на остановку/запуск бота | ||
// cancelAhead - Событие STOP будет отправлено в канал за cancelAhead до конца торгов | ||
cancelAhead := time.Minute * 5 | ||
t := investgo.NewTimer(client, "MOEX", cancelAhead) | ||
|
||
// запуск таймера | ||
wg.Add(1) | ||
go func(ctx context.Context) { | ||
defer wg.Done() | ||
err := t.Start(ctx) | ||
if err != nil { | ||
logger.Errorf(err.Error()) | ||
} | ||
}(ctx) | ||
|
||
// по сигналам останавливаем таймер | ||
go func() { | ||
<-sigs | ||
t.Stop() | ||
}() | ||
|
||
// чтение событий от таймера и управление ботом | ||
events := t.Events() | ||
wg.Add(1) | ||
go func(ctx context.Context) { | ||
defer wg.Done() | ||
for { | ||
select { | ||
case <-ctx.Done(): | ||
return | ||
case ev, ok := <-events: | ||
if !ok { | ||
return | ||
} | ||
logger.Infof("got event = %v", ev) | ||
switch ev { | ||
case investgo.START: | ||
// запуск бота | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
err = botOnOrderBook.Run() | ||
if err != nil { | ||
logger.Errorf(err.Error()) | ||
} | ||
}() | ||
case investgo.STOP: | ||
// остановка бота | ||
botOnOrderBook.Stop() | ||
} | ||
} | ||
} | ||
}(ctx) | ||
|
||
wg.Wait() | ||
} |
Oops, something went wrong.