diff --git a/bot/bot.go b/bot/bot.go index 23a211e..3209f40 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -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() } diff --git a/config/config.go b/config/config.go index 10ff9e7..fb782eb 100644 --- a/config/config.go +++ b/config/config.go @@ -15,7 +15,9 @@ var ( CheckDelay float64 CryptoPrefix string FiatPrefix string - LimitOrderMode bool + + LimitOrderMode bool + LimitOrderRetryCount int ) var logger = log.WithFields(log.Fields{ @@ -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" diff --git a/kraken/purchase.go b/kraken/purchase.go index b47a24e..4c99e40 100644 --- a/kraken/purchase.go +++ b/kraken/purchase.go @@ -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 }