Skip to content

Commit

Permalink
refactor: cleanup purchase logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Primexz committed May 17, 2024
1 parent 1633982 commit 3157654
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 80 deletions.
2 changes: 1 addition & 1 deletion bot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (b *Bot) run() {
if (b.timeOfNextOrder.Before(time.Now()) || newFiatMoney) && !b.initialRun {
b.log.Info("Placing bitcoin purchase order. ₿")

b.krakenApi.BuyBtc(0)
b.krakenApi.BuyBtc()
b.calculateTimeOfNextOrder()
}

Expand Down
5 changes: 4 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ var (
CheckDelay float64
CryptoPrefix string
FiatPrefix string
LimitOrderMode bool

LimitOrderMode bool
LimitOrderRetryCount int
)

var logger = log.WithFields(log.Fields{
Expand All @@ -31,6 +33,7 @@ func LoadConfiguration() {
KrakenOrderSize = loadFloatEnvVariableWithFallback("KRAKEN_ORDER_SIZE", 0.0001) // https://support.kraken.com/hc/en-us/articles/205893708-Minimum-order-size-volume-for-trading
CheckDelay = loadFloatEnvVariableWithFallback("CHECK_DELAY", 60)
LimitOrderMode = loadBoolEnvVariableWithFallback("LIMIT_ORDER_MODE", false)
LimitOrderRetryCount = int(loadFloatEnvVariableWithFallback("LIMIT_ORDER_RETRY_COUNT", 8))

if Currency == "USD" || Currency == "EUR" || Currency == "GBP" {
CryptoPrefix = "X"
Expand Down
164 changes: 86 additions & 78 deletions kraken/purchase.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,105 +6,113 @@ import (
"strings"
"time"

"github.com/aopoltorzhicky/go_kraken/rest"
"github.com/primexz/KrakenDCA/config"
"github.com/primexz/KrakenDCA/notification"
)

func (k *KrakenApi) BuyBtc(_retry_do_not_use int) {
if _retry_do_not_use > 5 {
k.log.Error("Failed to buy btc after 5 retries, stop recursion")
return
func (k *KrakenApi) BuyBtc() {
if config.LimitOrderMode {
for i := 0; i < config.LimitOrderRetryCount; i++ {
fiatPrice, err := k.GetCurrentBtcFiatPrice()
if err != nil {
k.log.Error("Failed to get current btc price", err)
}

if k.placeLimitOrder(fiatPrice) {
k.log.Info("Successfully bought limit btc")
return
}

k.log.Warn("Retrying to place limit order")
}

notification.SendPushNotification("Failed to buy btc", fmt.Sprintf("Failed to buy btc after %d retries", config.LimitOrderRetryCount))
k.log.Errorf("Failed to buy btc after %d retries", config.LimitOrderRetryCount)
} else {
if !k.placeMarketOrder() {
notification.SendPushNotification("Failed to buy btc", "Failed to buy btc")
k.log.Error("Failed to buy btc")
}
}
}

// placeMarketOrder places a market order on kraken
func (k *KrakenApi) placeMarketOrder() bool {
if _, err := k.api.AddOrder("xbt"+strings.ToLower(config.Currency), "buy", "market", config.KrakenOrderSize, nil); err != nil {
k.log.Error("Failed to buy btc", err.Error())
return false
}

currency := config.Currency
return true
}

// placeLimitOrder places a limit order on kraken
// returns true if order was successfully placed
// returns false if order was not placed and should be retried
func (k *KrakenApi) placeLimitOrder(fiatPrice float64) bool {
priceRound, _ := strconv.ParseFloat(fmt.Sprintf("%.1f", fiatPrice), 64)
args := map[string]interface{}{
// if set to true, no order will be submitted
"validate": false,

//price can only be specified up to 1 decimals
"price": priceRound - 0.1,
"oflags": "post",
"timeinforce": "GTD",
"expiretm": "+240", // close order after 4 minutes
}

fiatPrice, err := k.GetCurrentBtcFiatPrice()
response, err := k.api.AddOrder("xbt"+strings.ToLower(config.Currency), "buy", "limit", config.KrakenOrderSize, args)
if err != nil {
k.log.Error("Failed to get current btc price", err.Error())
k.log.Error("Failed to buy btc", err)
return false
}

var (
response rest.AddOrderResponse
krakenErr error
)
transactionId := response.TransactionIds[0]

if config.LimitOrderMode {
priceRound, _ := strconv.ParseFloat(fmt.Sprintf("%.1f", fiatPrice), 64)
args := map[string]interface{}{
// if set to true, no order will be submitted
"validate": false,

//price can only be specified up to 1 decimals
"price": priceRound - 0.1,
"oflags": "post",
"timeinforce": "GTD",
"expiretm": "+240", // close order after 4 minutes
for {
orderInfo, err := k.api.QueryOrders(true, "", transactionId)
if err != nil {
k.log.Error("Failed to get order status", err.Error())
return false
}

response, krakenErr = k.api.AddOrder("xbt"+strings.ToLower(currency), "buy", "limit", config.KrakenOrderSize, args)
if krakenErr != nil {
k.log.Error("Failed to buy btc", krakenErr.Error())
return
}
order, ok := orderInfo[transactionId]
if ok {
orderStatus := order.Status
k.log.Info("Order status:", orderStatus)

transactionId := response.TransactionIds[0]
if orderStatus == "closed" {
k.log.Info("Order successfully executed")
break
}

for {
orderInfo, err := k.api.QueryOrders(true, "", transactionId)
if err != nil {
k.log.Error("Failed to get order status", err.Error())
return
if orderStatus == "canceled" && order.Reason == "User requested" {
k.log.Info("Order canceled by user")
return true
}

order, ok := orderInfo[transactionId]
if ok {
orderStatus := order.Status
k.log.Info("Order status:", orderStatus)

if orderStatus == "closed" {
k.log.Info("Order successfully executed")
break // don't return to send notification and log
}

if orderStatus == "canceled" && order.Reason == "User requested" {
k.log.Info("Order canceled by user")
return
}

if orderStatus == "canceled" && order.Reason == "Post only order" {
k.log.Info("Order canceled by kraken due to post only order, retrying with new order")
k.BuyBtc(_retry_do_not_use + 1)
return
}

if orderStatus == "canceled" {
k.log.Info("Unknown reason for order cancelation.")
return
}

if orderStatus == "expired" {
k.log.Info("Order expired, retrying with new order")
k.BuyBtc(_retry_do_not_use + 1)
return
}
} else {
k.log.Error("Failed to query order status")
if orderStatus == "canceled" && order.Reason == "Post only order" {
k.log.Info("Order canceled by kraken due to post only order, retrying with new order")
return false
}

//wait on pending, open
time.Sleep(5 * time.Second)
}
if orderStatus == "canceled" {
k.log.Info("Unknown reason for order cancelation.")
return true
}

} else {
response, krakenErr = k.api.AddOrder("xbt"+strings.ToLower(currency), "buy", "market", config.KrakenOrderSize, nil)
if krakenErr != nil {
k.log.Error("Failed to buy btc", krakenErr.Error())
return
if orderStatus == "expired" {
k.log.Info("Order expired, retrying with new order")
return false
}
} else {
k.log.Error("Failed to query order status")
}
}

notification.SendPushNotification("BTC bought", fmt.Sprintf("Description: %s\nPrice: %f %s", response.Description.Info, fiatPrice, currency))
//wait on pending, open
time.Sleep(5 * time.Second)
}

k.log.Info("Successfully bought btc ->", response.Description.Info, response.Description.Price)
return true
}

0 comments on commit 3157654

Please sign in to comment.