Skip to content

Commit

Permalink
feat(telegram): buy and sell commands
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigo-brito committed Aug 15, 2021
1 parent f14b22f commit 50e8fa3
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 12 deletions.
32 changes: 32 additions & 0 deletions pkg/exchange/binance.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,38 @@ func (b *Binance) OrderMarket(side model.SideType, symbol string, quantity float
}, nil
}

func (b *Binance) OrderMarketQuote(side model.SideType, symbol string, quantity float64) (model.Order, error) {
err := b.validate(side, model.OrderTypeMarket, symbol, quantity, nil)
if err != nil {
return model.Order{}, err
}

order, err := b.client.NewCreateOrderService().
Symbol(symbol).
Type(binance.OrderTypeMarket).
Side(binance.SideType(side)).
QuoteOrderQty(fmt.Sprintf("%.f", quantity)).
NewOrderRespType(binance.NewOrderRespTypeFULL).
Do(b.ctx)
if err != nil {
return model.Order{}, err
}

cost, _ := strconv.ParseFloat(order.CummulativeQuoteQuantity, 64)
quantity, _ = strconv.ParseFloat(order.ExecutedQuantity, 64)
return model.Order{
ExchangeID: order.OrderID,
CreatedAt: time.Unix(0, order.TransactTime*int64(time.Millisecond)),
UpdatedAt: time.Unix(0, order.TransactTime*int64(time.Millisecond)),
Symbol: order.Symbol,
Side: model.SideType(order.Side),
Type: model.OrderType(order.Type),
Status: model.OrderStatusType(order.Status),
Price: cost / quantity,
Quantity: quantity,
}, nil
}

func (b *Binance) Cancel(order model.Order) error {
_, err := b.client.NewCancelOrderService().
Symbol(order.Symbol).
Expand Down
4 changes: 4 additions & 0 deletions pkg/exchange/paperwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@ func (p *PaperWallet) OrderMarket(side model.SideType, symbol string, size float
return order, nil
}

func (p *PaperWallet) OrderMarketQuote(side model.SideType, symbol string, quantity float64) (model.Order, error) {
return p.OrderMarket(side, symbol, quantity/p.lastCandle[symbol].Close)
}

func (p *PaperWallet) Cancel(order model.Order) error {
p.Lock()
defer p.Unlock()
Expand Down
4 changes: 2 additions & 2 deletions pkg/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ type Order struct {
}

func (o Order) String() string {
return fmt.Sprintf("%s %s | ID: %d, Type: %s - %f x $%f (%s)",
o.Side, o.Symbol, o.ID, o.Type, o.Quantity, o.Price, o.Status)
return fmt.Sprintf("[%s] %s %s | ID: %d, Type: %s, %f x $%f (~$%.f)",
o.Status, o.Side, o.Symbol, o.ID, o.Type, o.Quantity, o.Price, o.Quantity*o.Price)
}

type Account struct {
Expand Down
117 changes: 107 additions & 10 deletions pkg/notification/telegram.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package notification

import (
"fmt"
"regexp"
"strconv"
"strings"
"time"

Expand All @@ -14,6 +16,11 @@ import (
"github.com/rodrigo-brito/ninjabot/pkg/service"
)

var (
buyRegexp = regexp.MustCompile(`/buy\s+(?P<pair>\w+)\s+(?P<amount>[0-9]+(?:\.\d+)?)(?P<percent>%)?`)
sellRegexp = regexp.MustCompile(`/sell\s+(?P<pair>\w+)\s+(?P<amount>[0-9]+(?:\.\d+)?)(?P<percent>%)?`)
)

type telegram struct {
settings model.Settings
orderController *order.Controller
Expand Down Expand Up @@ -174,31 +181,120 @@ func (t telegram) ProfitHandle(m *tb.Message) {
return
}

for _, summary := range t.orderController.Results {
_, err := t.client.Send(m.Sender, fmt.Sprintf("`%s`", summary.String()))
for pair, summary := range t.orderController.Results {
_, err := t.client.Send(m.Sender, fmt.Sprintf("*PAIR*: `%s`\n`%s`", pair, summary.String()))
if err != nil {
log.Error(err)
}
}
}

func (t telegram) BuyHandle(m *tb.Message) {
_, err := t.client.Send(m.Sender, "not implemented yet")
match := buyRegexp.FindStringSubmatch(m.Text)
if len(match) == 0 {
_, err := t.client.Send(m.Sender, "Invalid command.\nExamples of usage:\n`/buy BTCUSDT 100`\n\n`/buy BTCUSDT 50%`")
if err != nil {
log.Error(err)
}
return
}

command := make(map[string]string)
for i, name := range buyRegexp.SubexpNames() {
if i != 0 && name != "" {
command[name] = match[i]
}
}

pair := strings.ToUpper(command["pair"])
amount, err := strconv.ParseFloat(command["amount"], 64)
if err != nil {
log.Error(err)
t.OrError(err)
return
} else if amount <= 0 {
_, err := t.client.Send(m.Sender, "Invalid amount")
if err != nil {
log.Error(err)
}
return
}
}

func (t telegram) StatusHandle(m *tb.Message) {
status := t.orderController.Status()
_, err := t.client.Send(m.Sender, fmt.Sprintf("Status: `%s`", status))
if command["percent"] != "" {
_, quote, err := t.orderController.Position(pair)
if err != nil {
t.OrError(err)
return
}

amount = amount * quote / 100.0
}

order, err := t.orderController.OrderMarketQuote(model.SideTypeBuy, pair, amount)
if err != nil {
log.Error(err)
t.OrError(err)
return
}
t.OnOrder(order)
}

func (t telegram) SellHandle(m *tb.Message) {
_, err := t.client.Send(m.Sender, "not implemented yet")
match := sellRegexp.FindStringSubmatch(m.Text)
if len(match) == 0 {
_, err := t.client.Send(m.Sender, "Invalid command.\nExample of usage:\n`/sell BTCUSDT 100`\n\n`/sell BTCUSDT 50%")
if err != nil {
log.Error(err)
}
return
}

command := make(map[string]string)
for i, name := range sellRegexp.SubexpNames() {
if i != 0 && name != "" {
command[name] = match[i]
}
}

pair := strings.ToUpper(command["pair"])
amount, err := strconv.ParseFloat(command["amount"], 64)
if err != nil {
t.OrError(err)
return
} else if amount <= 0 {
_, err := t.client.Send(m.Sender, "Invalid amount")
if err != nil {
log.Error(err)
}
return
}

if command["percent"] != "" {
asset, _, err := t.orderController.Position(pair)
if err != nil {
t.OrError(err)
return
}

amount = amount * asset / 100.0
order, err := t.orderController.OrderMarket(model.SideTypeSell, pair, amount)
if err != nil {
t.OrError(err)
return
}
t.OnOrder(order)
return
}

order, err := t.orderController.OrderMarketQuote(model.SideTypeSell, pair, amount)
if err != nil {
t.OrError(err)
return
}
t.OnOrder(order)
}

func (t telegram) StatusHandle(m *tb.Message) {
status := t.orderController.Status()
_, err := t.client.Send(m.Sender, fmt.Sprintf("Status: `%s`", status))
if err != nil {
log.Error(err)
}
Expand Down Expand Up @@ -251,6 +347,7 @@ func (t telegram) OnOrder(order model.Order) {
}

func (t telegram) OrError(err error) {
log.Error(err)
title := "🛑 ERROR"
message := fmt.Sprintf("%s\n-----\n%s", title, err)
t.Notify(message)
Expand Down
27 changes: 27 additions & 0 deletions pkg/order/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,33 @@ func (c *Controller) OrderLimit(side model.SideType, symbol string, size, limit
return order, nil
}

func (c *Controller) OrderMarketQuote(side model.SideType, symbol string, amount float64) (model.Order, error) {
c.mtx.Lock()
defer c.mtx.Unlock()

log.Infof("[ORDER] Creating MARKET %s order for %s", side, symbol)
order, err := c.exchange.OrderMarketQuote(side, symbol, amount)
if err != nil {
log.Errorf("order/controller exchange: %s", err)
return model.Order{}, err
}

err = c.createOrder(&order)
if err != nil {
log.Errorf("order/controller storage: %s", err)
return model.Order{}, err
}

// calculate profit
if order.Side == model.SideTypeSell {
c.processTrade(&order)
}

go c.orderFeed.Publish(order, true)
log.Infof("[ORDER CREATED] %s", order)
return order, err
}

func (c *Controller) OrderMarket(side model.SideType, symbol string, size float64) (model.Order, error) {
c.mtx.Lock()
defer c.mtx.Unlock()
Expand Down
1 change: 1 addition & 0 deletions pkg/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Broker interface {
OrderOCO(side model.SideType, symbol string, size, price, stop, stopLimit float64) ([]model.Order, error)
OrderLimit(side model.SideType, symbol string, size float64, limit float64) (model.Order, error)
OrderMarket(side model.SideType, symbol string, size float64) (model.Order, error)
OrderMarketQuote(side model.SideType, symbol string, quote float64) (model.Order, error)
Cancel(model.Order) error
}

Expand Down

0 comments on commit 50e8fa3

Please sign in to comment.