Skip to content

Commit

Permalink
Merge pull request #5 from giansalex/notify-improvements
Browse files Browse the repository at this point in the history
Notifications
  • Loading branch information
giansalex authored Jul 27, 2021
2 parents aae81c8 + 7e5e56c commit 4dc2e26
Show file tree
Hide file tree
Showing 15 changed files with 325 additions and 136 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ jobs:
uses: actions/checkout@v2

- name: Build
run: go build -v .
run: go build -v -o binance ./app
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ RUN apk update && \
COPY . .

# Build executable
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -gcflags=all=-l -o /build/binance
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -gcflags=all=-l -o /build/binance ./app

# Release image
FROM scratch
Expand Down
68 changes: 57 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,72 @@ For sell orders with static stoploss
./binance -pair=BTC/USDT -price=9400 -amount=0.1
```

Use telegram for notifications.
## Notifications

- Telegram.
```sh
./binance -pair=BTC/USDT -percent=3 -interval=60 -telegram.chat=<user-id>
```
> For get user id, talk o the [userinfobot](https://t.me/userinfobot)
![telegram binance bot](https://user-images.githubusercontent.com/14926587/127190283-b7117dd2-dd03-421b-ae49-95997503ae67.png)

> For get user id, talk to the [userinfobot](https://t.me/userinfobot)

- Mailing.
```sh
./binance -pair=BTC/USDT -percent=3 \
-mail.host="smtp.example.com" \
-mail.port=587 \
-mail.user="[email protected]" \
-mail.pass="xxxx" \
-mail.from="[email protected]" \
-mail.to="[email protected]"
```

> You can notify both: telegram, mail.
## Docker

You can run in docker container.
```bash
docker pull giansalex/binance-stoploss
# create container
docker run -d --name binance_sell_BTC giansalex/binance-stoploss \
-type=BUY \
-pair=BTC/USDT \
-percent=5 \
-amount=0.01
```

List available parameters
```sh
-type string
order type: SELL or BUY (default: SELL)
-pair string
market pair, example: BNB/USDT
-amount string
(optional) amount to order (sell or buy) on stoploss
-interval int
interval in seconds to update price, example: 30 (30 sec.) (default 30)
-price float
price (for static stoploss)
-mail.from string
(optional) email sender
-mail.host string
(optional) SMTP Host
-mail.pass string
(optional) SMTP Password
-mail.port int
(optional) SMTP Port (default 587)
-mail.to string
(optional) email receptor
-mail.user string
(optional) SMTP User
-pair string
market pair, example: BNB/USDT
-percent float
percent (for trailing stoploss), example: 3.0 (3%)
-amount float
(optional) amount to order (sell or buy) on stoploss, default all balance
percent (for trailing stop loss), example: 3.0 (3%)
-price float
price (for static stop loss), example: 9200.00 (BTC price)
-stop-change
Notify on stoploss change (default: false)
-telegram.chat int
(optional) telegram User ID for notify
-type string
order type: SELL or BUY (default "SELL")

```
90 changes: 90 additions & 0 deletions app/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package cmd

import (
"context"
"flag"
"log"
"os"
"strings"
"time"

binance "github.com/adshao/go-binance/v2"
"github.com/giansalex/binance-stoploss/notify"
"github.com/giansalex/binance-stoploss/stoploss"
"github.com/hashicorp/go-retryablehttp"
)

var (
typePtr = flag.String("type", "SELL", "order type: SELL or BUY")
pairPtr = flag.String("pair", "", "market pair, example: BNB/USDT")
percentPtr = flag.Float64("percent", 0.00, "percent (for trailing stop loss), example: 3.0 (3%)")
pricePtr = flag.Float64("price", 0.00, "price (for static stop loss), example: 9200.00 (BTC price)")
intervalPtr = flag.Int("interval", 30, "interval in seconds to update price, example: 30 (30 sec.)")
amountPtr = flag.String("amount", "", "(optional) amount to order (sell or buy) on stoploss")
notifyChangesPtr = flag.Bool("stop-change", false, "Notify on stoploss change (default: false)")
chatPtr = flag.Int64("telegram.chat", 0, "(optional) telegram User ID for notify")
mailHostPtr = flag.String("mail.host", "", "(optional) SMTP Host")
mailPortPtr = flag.Int("mail.port", 587, "(optional) SMTP Port")
mailUserPtr = flag.String("mail.user", "", "(optional) SMTP User")
mailPassPtr = flag.String("mail.pass", "", "(optional) SMTP Password")
mailFromPtr = flag.String("mail.from", "", "(optional) email sender")
mailToPtr = flag.String("mail.to", "", "(optional) email receptor")
)

func Execute() {
flag.Parse()
apiKey := os.Getenv("BINANCE_APIKEY")
secret := os.Getenv("BINANCE_SECRET")

if apiKey == "" || secret == "" {
log.Fatal("BINANCE_APIKEY, BINANCE_SECRET are required")
}

if pairPtr == nil || *pairPtr == "" {
log.Fatal("pair market is required")
}

if (percentPtr == nil || *percentPtr <= 0) && (pricePtr == nil || *pricePtr <= 0) {
log.Fatal("a price or percent parameter is required")
}

retryClient := retryablehttp.NewClient()
retryClient.Logger = nil
retryClient.RetryMax = 10
api := binance.NewClient(apiKey, secret)
api.HTTPClient = retryClient.StandardClient()
notifier := buildNotify()
config := &stoploss.Config{
OrderType: strings.ToUpper(*typePtr),
Market: *pairPtr,
Price: *pricePtr,
Quantity: *amountPtr,
StopFactor: *percentPtr / 100,
NotifyStopChange: *notifyChangesPtr,
}
trailing := stoploss.NewTrailing(stoploss.NewExchange(context.Background(), api), notifier, &notify.LogNotify{}, config)

for {
if trailing.RunStop() {
break
}

time.Sleep(time.Duration(*intervalPtr) * time.Second)
}
}

func buildNotify() notify.SingleNotify {
notifiers := []notify.SingleNotify{}

tgToken := os.Getenv("TELEGRAM_TOKEN")
if *chatPtr != 0 && tgToken != "" {
notifiers = append(notifiers, notify.NewTelegramNotify(tgToken, *chatPtr))
}

if *mailHostPtr != "" && *mailUserPtr != "" && *mailPassPtr != "" && *mailFromPtr != "" && *mailToPtr != "" {
subject := "Binance StopLoss Bot"
notifiers = append(notifiers, notify.NewMailNotify(*mailHostPtr, *mailPortPtr, *mailUserPtr, *mailPassPtr, subject, *mailFromPtr, *mailToPtr))
}

return stoploss.NewNotify(notifiers)
}
7 changes: 7 additions & 0 deletions app/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/giansalex/binance-stoploss/app/cmd"

func main() {
cmd.Execute()
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/hashicorp/go-retryablehttp v0.6.8
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
github.com/adshao/go-binance v0.0.0-20200724005944-6a0c3995d85c h1:jATQCjUKK8rHiGiIkrWXuL5WXacXjPSDeQoffGlc7PU=
github.com/adshao/go-binance v0.0.0-20200724005944-6a0c3995d85c/go.mod h1:XlIpE7brbCEQxp6VRouG/ZgjLjygQWE1xnc1DtQNp6I=
github.com/adshao/go-binance/v2 v2.2.1 h1:H5tfXqDu+bx1mZqJP+5gc8ahidgDFvj683M3RStg9C4=
github.com/adshao/go-binance/v2 v2.2.1/go.mod h1:o+84WK3DQxq9vEKV9ncRcQi+J7RFCGhM27osbECZiJQ=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -29,9 +26,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
57 changes: 0 additions & 57 deletions main.go

This file was deleted.

24 changes: 24 additions & 0 deletions notify/log_notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package notify

import (
"log"
"regexp"
)

const regex = `<.*?>`

type LogNotify struct {
}

// Send notify to log
func (lnotify *LogNotify) Send(message string) error {
log.Println(lnotify.clearHtml(message))

return nil
}

// This method uses a regular expresion to remove HTML tags.
func (lnotify *LogNotify) clearHtml(s string) string {
r := regexp.MustCompile(regex)
return r.ReplaceAllString(s, "")
}
39 changes: 39 additions & 0 deletions notify/mail_notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package notify

import (
"strings"

"gopkg.in/gomail.v2"
)

type MailNotify struct {
sender gomail.SendCloser
subject string
from string
to string
}

func NewMailNotify(host string, port int, username, password, subject, from, to string) *MailNotify {
d := gomail.NewDialer(host, port, username, password)
s, err := d.Dial()
if err != nil {
panic(err)
}

return &MailNotify{s, subject, from, to}
}

// Send notify to email
func (mailNotify *MailNotify) Send(message string) error {
m := gomail.NewMessage()
m.SetHeader("From", mailNotify.from)
m.SetHeader("To", mailNotify.to)
m.SetHeader("Subject", mailNotify.subject)
m.SetBody("text/html", mailNotify.fixLineEnding(message))

return gomail.Send(mailNotify.sender, m)
}

func (mailNotify *MailNotify) fixLineEnding(msg string) string {
return strings.ReplaceAll(msg, "\n", "<br/>")
}
5 changes: 5 additions & 0 deletions notify/single_notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package notify

type SingleNotify interface {
Send(message string) error
}
29 changes: 29 additions & 0 deletions notify/telegram_notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package notify

import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)

type TelegramNotify struct {
token string
chatID int64
}

func NewTelegramNotify(token string, chatID int64) *TelegramNotify {
return &TelegramNotify{token: token, chatID: chatID}
}

// Send notify to telegram
func (tgNotify *TelegramNotify) Send(message string) error {
bot, err := tgbotapi.NewBotAPI(tgNotify.token)
if err != nil {
return err
}

msg := tgbotapi.NewMessage(tgNotify.chatID, message)
msg.ParseMode = "html"

_, err = bot.Send(msg)

return err
}
Loading

0 comments on commit 4dc2e26

Please sign in to comment.