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

Made with Slides.com