Arquitetando um bot para negociar criptomoedas
Meetup Go BH. Set, 2021
RODRIGO BRITO
www.brito.com.br
/rodrigo-brito
@RodrigoFBrito
Mestre em Ciência da Computação
Gerente de desenvolvimento
Entusiasta de criptomoedas
Antes de começar...
Esta palestra
não é sobre pirâmides
Esta palestra não é sobre tranformar R$1.5K em R$1M
É apenas um estudo de caso de aplicação de Go
TLDR;
Ninjabot: a revolta de um desenvolvedor que não encontrou o bot que queria
Smart Contracts
Memes
Privacidade
COMO FUNCIONA?
Exchange
Uma criptomoeda não vale nada...
...até que alguém pague algo por ela!
OFERTA E DEMANDA
Mercado de Criptomoedas
- 24 horas / 7 dias por semana
- Altamente volátil
- Alta liquidez
- Exige respostas rápidas
Poxa Rodrigo, mas eu não sou um robô...
BÁSICO
COMPRA
VENDE
INDICADORES
Gatilhos - Moving Average
COMPRA
VENDE
Filtros - Moving Average
Alerta - URSO
TRADING BOT
Bots populares
- Metatrader (Código fechado, GUI, Windows, C like)
- Freqtrade (Open Source, CLI, Multiplataforma, Python)
- Gekko (Open Source, CLI, Multiplataforma, JS)
- Backtrader (Open Source, CLI, Multiplataforma, Python)
- Gocryptotrader (Open Source, RPC, Multiplataforma, Go)
Backtrader
Funcionalidades comuns
- Criar estratégias automatizadas
- Permitir simalações (Backtest)
- Visualizar gráficos
- Carteira Fake (Paper Wallet)
- Controle Remoto (Telegram, app, etc.)
- Notificações (Email, Telegram, etc.)
Mas o que falta?
Gerenciamento de Risco
Estratégia A
Perder 10 reais
Probabilidade: 60%
Ganhar 30 reais
Probabilidade: 40%
Estratégia B
Perder 20 reais
Probabilidade: 50%
Ganhar 20 reais
Probabilidade: 50%
O que vocês preferem?
Controle de risco
Agora que temos um escopo, mãos a massa!
Estrutura Básica
- Candles
- Atualização de ordens
- Carteira de ativos
- Estratégia
- Notificações
- Controle / estatística
- Compra e Venda
- Agendamento de ordens
Cenário 1: Produção
Cenário 2: Backtest
Dataset CSV
Carteira Virtual
Cenário 3: Simulação
Carteira Virtual
Implementação
Interface
Interface
Composição
Comunicação
Pub-sub Pattern
Data Feed
Order Feed
type Feeder interface {
// Load historical data
CandlesByPeriod(...) ([]Candle, error)
// Real-time feed
CandlesSubscription(...) (chan Candle, chan error)
}
- BinanceFeed
- CSVFeed
- SQLFeed
- etc.
Data Feed
type Broker interface {
Account() (model.Account, error)
Position(symbol string) (asset, quote float64, err error)
Order(symbol string, id int64) (Order, error)
CreateOrderOCO(...) ([]Order, error)
CreateOrderLimit(...) (Order, error)
CreateOrderMarket(...) (model.Order, error)
CreateOrderMarketQuote(...) (Order, error)
Cancel(...) error
}
- BinanceBroker
- Virtual Wallet
- etc.
Broker
type Exchange interface {
Feeder // Binance, CSV, SQLite, etc
Broker // Binance, Virtual Wallet, etc
}
Exchange
type NinjaBot struct {
storage Storage
settings Settings
exchange Exchange
strategy Strategy
}
type Option func(*NinjaBot)
func NewBot(ctx context.Context, e Exchange, s Strategy, options ...Option) (*NinjaBot, error) {
...
for _, option := range options {
option(bot)
}
...
}
Options Pattern
// Without customization
bot, err := ninjabot.NewBot(ctx, binance, strategy)
if err != nil {
log.Error(err)
}
// With options
bot, err := ninjabot.NewBot(
ctx,
binance,
strategy,
ninjabot.WithCustomStorage(storage),
ninjabot.WithLogLevel(log.WarnLevel),
ninjabot.WithNotifier(telegram),
)
if err != nil {
log.Error(err)
}
Uso da biblioteca
type OrderSubscriber interface {
OnOrder(Order)
}
type CandleSubscriber interface {
OnCandle(Candle)
}
Pub-sub Consumers
func WithOrderSubscription(subscriber OrderSubscriber) Option {
return func(bot *NinjaBot) {
bot.SubscribeOrder(subscriber)
}
}
func (n *NinjaBot) SubscribeOrder(subscriptions ...OrderSubscriber) {
for _, pair := range n.settings.Pairs {
for _, s := range subscriptions {
n.orderFeed.Subscribe(pair, s.OnOrder)
}
}
}
Pub-sub Consumers
ccandle := make(chan model.Candle)
cerr := make(chan error)
go func() {
retry := &backoff.Backoff{ Min: 100 * time.Millisecond, Max: time.Second }
for {
done, _, err := binance.WsKlineServe(pair, period, func(event *binance.WsKlineEvent) {
retry.Reset()
ccandle <- CandleFromWsKline(symbol, event.Kline)
}, func(err error) {
cerr <- err
})
if err != nil {
cerr <- err
close(cerr)
close(ccandle)
return
}
select {
case <-ctx.Done():
close(cerr)
close(ccandle)
return
case <-done:
time.Sleep(retry.Duration())
}
}
}()
Integração com Exchange
github.com/adshao/go-binance
type Strategy interface {
Timeframe() string
WarmupPeriod() int
Indicators(dataframe *model.Dataframe)
OnCandle(*model.Dataframe, service.Broker)
}
Estratégia
type Series []float64
type Dataframe struct {
Pair string
Close Series
Open Series
High Series
Low Series
Volume Series
Time []time.Time
LastUpdate time.Time
// Custom user metadata
Metadata map[string]Series
}
Estratégia
func (e CrossEMA) WarmupPeriod() int {
return 9
}
func (e CrossEMA) Indicators(df *ninjabot.Dataframe) {
df.Metadata["ema9"] = talib.Ema(df.Close, 9)
}
func (e *CrossEMA) OnCandle(df *ninjabot.Dataframe, broker service.Broker) {
if df.Close.Crossover(df.Metadata["ema9"]) {
_, err := broker.CreateOrderMarketQuote(ninjabot.SideTypeBuy, df.Pair, 100)
if err != nil {
log.Error(err)
}
}
if df.Close.Crossunder(df.Metadata["ema9"]) {
_, err := broker.CreateOrderMarket(ninjabot.SideTypeSell, df.Pair, 100)
if err != nil {
log.Error(err)
}
}
}
Estratégia
Plugins - Gráficos
Plugins - Telegram
Hora do show!
Live Code
@RodrigoFBrito
github.com/rodrigo-brito
brito.com.br
Arquitetando um bot para negociar criptomoedas
By Rodrigo Brito
Arquitetando um bot para negociar criptomoedas
- 1,168