Projeto de um plataforma de negociação

 

System Design Meetup, Maio 2022.

RODRIGO BRITO

www.brito.com.br

/rodrigo-brito

@RodrigoFBrito

Mestre em Ciência da Computação

Dev / Manager

Smart Contracts

Memes

Privacidade

COMO FUNCIONA?

Exchange

BÁSICO

COMPRA

VENDE

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?

Bola de Cristal Prática

COMPRA

VENDE

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

Requisitos

  • Lógica Customizável
  • Permitir simalações (Backtest)
  • Fácil distribuição (Linux, standalone)
  • Plugins (Gráficos, Telegram, etc)
  • Genérico (Bolsa de Valores?)

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

Solução GH

while isConnected() {
    // recebe nova cotação
    candle = next()
    
    // lógica da bola de cristal
    if isBullish(candle) {
    	buy("BTCUSDT", 100)
    } else {
    	sell("BTCUSDT", 100)
    }    	
}

Solução GH

while isConnected() {
    // recebe nova cotação
    candle = next()
    
    // lógica da bola de cristal
    if isBullish(candle) {
    	buy("BTCUSDT", 100)
    } else {
    	sell("BTCUSDT", 100)
    }    	
}

Websocket (~2s)

Solução GH

while isConnected() {
    // recebe nova cotação
    candle = next()
    
    // lógica da bola de cristal
    if isBullish(candle) {
    	buy("BTCUSDT", 100)
    } else {
    	sell("BTCUSDT", 100)
    }    	
}

I/O?

Gráficos?

Notificações?

Solução GH

BTC

ETH

N

...

Desafios / Soluções

  • Gargalo / Múltiplos pares
    • Processamento Assíncrono
    • Comunicação por eventos

 

  • Garantia de ordem de processamento
    • Estrutura de Dados (Priority Queue)

 

  • Persistência
    • BuntDB (embedável, rápido, versátil)

E para validar a bola de cristal?

Cenário 1: Produção

Cenário 2: Backtest

Dataset CSV

Carteira Virtual

Cenário 3: Simulação

Carteira Virtual

Implementação

Módulo Abstrato

Módulo Abstrato

Módulo Concreto

Comunicação

Pub-sub Pattern

Tópicos

Cotação

Ordens

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, exchange, strategy)
if err != nil {
	log.Error(err)
}


// With options
bot, err := ninjabot.NewBot(
	ctx,
	exchange,
	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

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())
		}
	}
}()

Websockets / Go Channels

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

@RodrigoFBrito

github.com/rodrigo-brito

brito.com.br

Projeto de uma plataforma de negociações

By Rodrigo Brito

Projeto de uma plataforma de negociações

  • 525