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
- 656