Giter Club home page Giter Club logo

go_kraken's Introduction

Kraken Go

Go library for Kraken Websocket and REST API.

ATTENTION! Version 0.2.0 is a union of websocket and rest package. Now go_kraken is the one package contains websocket and rest API.

Installation package

go get github.com/aopoltorzhicky/go_kraken

Usage

To learn how you can use the package read examples.

Websocket API

For quick start read the one below:

package main

import (
	"os"
	"os/signal"
	"syscall"

	log "github.com/sirupsen/logrus"

	ws "github.com/aopoltorzhicky/go_kraken/websocket"
)

func main() {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)

	kraken := ws.NewKraken(ws.ProdBaseURL)
	if err := kraken.Connect(); err != nil {
		log.Fatalf("Error connecting to web socket: %s", err.Error())
	}

	// subscribe to BTCUSD`s ticker
	if err := kraken.SubscribeTicker([]string{ws.BTCUSD}); err != nil {
		log.Fatalf("SubscribeTicker error: %s", err.Error())
	}

	for {
		select {
		case <-signals:
			log.Warn("Stopping...")
			if err := kraken.Close(); err != nil {
				log.Fatal(err)
			}
			return
		case update := <-kraken.Listen():
			switch data := update.Data.(type) {
			case ws.TickerUpdate:
				log.Printf("----Ticker of %s----", update.Pair)
				log.Printf("Ask: %s with %s", data.Ask.Price.String(), data.Ask.Volume.String())
				log.Printf("Bid: %s with %s", data.Bid.Price.String(), data.Bid.Volume.String())
				log.Printf("Open today: %s | Open last 24 hours: %s", data.Open.Today.String(), data.Open.Last24.String())
			default:
			}
		}
	}
}

Some options is available for Kraken object:

kraken := ws.NewKraken(
	ws.ProdBaseURL,
	ws.WithHeartbeatTimeout(10*time.Second), // set interval ping message sending. Should be less than read timeout. Default: 10s.
	ws.WithLogLevel(log.TraceLevel), // set logging level. Default: info.
	ws.WithReadTimeout(15*time.Second), // set read timeout. Default: 15s.
	ws.WithReconnectTimeout(5*time.Second),  // set interval of reconnecting after disconnect. Default: 5s.
)

To build order book by updates you can use OrderBook structure. Example of usage you can find here. Short code example:

// subscribe to BTCUSD`s book
if err := kraken.SubscribeBook([]string{ws.BTCUSD}, ws.Depth10); err != nil {
	log.Fatalf("SubscribeBook error: %s", err.Error())
}

// 10 - a depth of order book
// 5 - the price precision from asset info
// 8 - the volume precision from asset info
orderBook := ws.NewOrderBook(10, 5, 8)

for {
	select {
		case update := <-kraken.Listen():
			switch data := update.Data.(type) {
			case ws.OrderBookUpdate:
				// To apply updates call this method. true - is the checksum verification flag
				if err := orderBook.ApplyUpdate(data, true); err != nil {
					log.Fatal(err)
				}

				log.Print(orderBook.String())
			}			
	}
}

For private Webscoket API usage:

package main

import (
	"os"
	"os/signal"
	"syscall"

	log "github.com/sirupsen/logrus"

	ws "github.com/aopoltorzhicky/go_kraken/websocket"
)

func main() {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)

	// Create `Kraken` object
	kraken := ws.NewKraken(ws.AuthSandboxBaseURL)

	// Connect to server
	if err := kraken.Connect(); err != nil {
		log.Fatalf("Error connecting to web socket: %s", err.Error())
	}

	// Authenticate with your private keys
	if err := kraken.Authenticate(os.Getenv("KRAKEN_API_KEY"), os.Getenv("KRAKEN_SECRET")); err != nil {
		log.Fatalf("Authenticate error: %s", err.Error())
	}

	// Subscribe to channels or send commands
	if err := kraken.SubscribeOwnTrades(); err != nil {
		log.Fatalf("SubscribeOwnTrades error: %s", err.Error())
	}

	for {
		select {
		case <-signals:
			log.Warn("Stopping...")
			if err := kraken.Close(); err != nil {
				log.Fatal(err)
			}
			return
		case update := <-kraken.Listen():
			switch data := update.Data.(type) {
			case ws.OwnTradesUpdate:
				for i := range data {
					for tradeID, trade := range data[i] {
						log.Printf("Trade %s: %s", tradeID, trade.Type)
					}
				}
			case ws.OpenOrdersUpdate:
				for i := range data {
					for orderID, order := range data[i] {
						log.Printf("Order %s: %#v", orderID, order.Descr)
					}
				}
			default:
			}
		}
	}
}

REST API

To learn how to use REST API read example below:

package main

import (
	"log"

	"github.com/aopoltorzhicky/go_kraken/rest"
)

func main() {
	api := rest.New("", "")
	spread, err := api.GetSpread("ADAETH", 0)
	if err != nil {
		log.Fatalln(err)
	}
	log.Println(spread)

	t, err := api.Time()
	if err != nil {
		log.Fatalln(err)
	}
	log.Println(t)
}

go_kraken's People

Contributors

aopoltorzhicky avatar bbayszczak avatar byroncoetsee avatar codacy-badger avatar dependabot[bot] avatar ferluci avatar kjkraw avatar kostaspt avatar lightyear15 avatar lk16 avatar marioarranzr avatar mdarc avatar r1se avatar rdagnelie avatar rtreffer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

go_kraken's Issues

Received error "websocket: close 1013: Market data unavailable"

Hi!
I received this error and the client was not able to recover? I sent out an email to Kraken support to understand this error code a bit more. Is the correct way to handle this documented somewhere? Happy to make the change myself, just trying to gather information.

websocket: close 1013: Market data unavailable

Great job! :)

Thanks for this - looks very smooth. Heads up - I noticed that running the basic orderbook example gives an 'Unknown message type:' error inside client.handleChannel. Maybe I'm missing something?

Resubscribe fails for private channels

I noticed that my private feed was not able to re-subscribe after error. I noticed this error:
"Unsupported field: 'pair' for the given msg type: private subscribe: "

I think this solution might work...

func (k *Kraken) resubscribe() error {
	for _, sub := range k.subscriptions {
		switch sub.Subscription.Name {
		// Private Channels
		case ChanOwnTrades, ChanOpenOrders:
			return k.subscribeToPrivate(sub.Subscription.Name)
		default:
			//All other channels can do normal auth
			if err := k.send(SubscriptionRequest{
				Event:        EventSubscribe,
				Pairs:        []string{sub.Pair},
				Subscription: sub.Subscription,
			}); err != nil {
				return err
			}
		}
	}
	return nil
}

Can't parse open order websocket messages

Thank you very much for your work on this library.

I'm currently getting a "Can't parse data" error when receiving incoming websocket messages regarding open orders (listening for DataUpdates from AuthClient.SubscribeOpenOrders).

I believe the type assertion data.([]interface{}) is failing. I'm not sure if Kraken changed the message format recently?

Can't seem to avoid checksum failure

Hi. Thanks so much for this library.
I'm trying my best to implement a simple local orderbook, as all the examples suggest. However, after a few seconds, I keep hitting a checksum mismatch. If I increase the depth to 100, it lasts a whole lot longer but still crashes after about 30 minutes or so.

  • It's able to run for a few updates so the checksum calculations seem to be working.
  • I've also counted each iteration and it isn't skipping any. (see the 2 counters in the code)
  • I've compared my checksum maths to the Google Sheet provided by Kraken (here) and with my orderbook inputted, the checksum is the same (again, so my calculations are correct).

My only thought is processing speed but I'm on a 2.9GHz Quadcore i7 Macbook Pro, I don't think I should be having processing speed issues? Activity Monitor shows nothing hectic either.

Here's my code if anyone feels like lending an eye.

package kraken

import (
	"errors"
	"fmt"
	"hash/crc32"
	"os"
	"os/signal"
	e "router/exchanges"
	log "router/logging"
	"sort"
	"strings"
	"syscall"

	ws "github.com/aopoltorzhicky/go_kraken/websocket"
	"github.com/shopspring/decimal"
)

var orderbook_btc_usd = e.IndexedBook{
	Bids: make(map[float64]e.SubBook),
	Asks: make(map[float64]e.SubBook),
}

func ConnectKrakenWS() {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)

	kraken := ws.NewKraken(ws.ProdBaseURL)
	if err := kraken.Connect(); err != nil {
		log.LogFatal(errors.New("Error connecting to web socket: " + err.Error()))
	}

	// subscribe to BTCUSD`s ticker
	if err := kraken.SubscribeBook([]string{ws.BTCUSD}, ws.Depth10); err != nil {
		log.LogFatal(errors.New("SubscribeTicker error: " + err.Error()))
	}

	orderbook_btc_usd = e.IndexedBook{
		Bids: make(map[float64]e.SubBook),
		Asks: make(map[float64]e.SubBook),
	}

	var processedCounter = 0
	var recievedCounter = 0
	// busy := false

	for {
		select {
		case <-signals:
			log.LogInfo("Stopping kraken WS...")
			if err := kraken.Close(); err != nil {
				log.LogFatal(err)
			}
			return
		case update := <-kraken.Listen():

			switch data := update.Data.(type) {
			case ws.OrderBookUpdate:
				recievedCounter += 1
				// if !busy {
				// 	busy = true
				switch update.Pair {
				case "XBT/USD":
					handleBTCUSDUpdateEvent(&data)
				}
				processedCounter += 1
				// 	busy = false
				// }
			default:
			}
		}
	}
}

func handleBTCUSDUpdateEvent(v *ws.OrderBookUpdate) {

	capture(orderbook_btc_usd.Bids, v.Bids)
	capture(orderbook_btc_usd.Asks, v.Asks)

	newBook := sortorderbook()
	newBook.Pair.Currency1 = e.BTC
	newBook.Pair.Currency2 = e.USD

	if !v.IsSnapshot {
		if !isBookValid(newBook, v.CheckSum) {
			fmt.Println("Checksum failure. Expected", v.CheckSum)
			// log.LogFatal(errors.New("Kraken checksum failure"))
			return
		}
	}

	e.Kraken.Book = newBook
	e.Kraken.Active = true
}

func capture(liveSideCollection map[float64]e.SubBook, deltaCollection []ws.OrderBookItem) {
	// for k, v := range liveSideCollection {
	for i := range deltaCollection {
		price, _ := deltaCollection[i].Price.Float64()
		qty, _ := deltaCollection[i].Volume.Float64()
		if qty == 0 {
			delete(liveSideCollection, price)
		} else {
			liveSideCollection[price] = e.SubBook{
				Price: price,
				Qty:   qty,
			}
		}
	}
	// }
}

func sortorderbook() (Book e.Book) {
	c := orderbook_btc_usd

	Book.Bids = []e.SubBook{}
	for i := range c.Bids {
		Book.Bids = append(Book.Bids, e.SubBook{
			Price: c.Bids[i].Price,
			Qty:   c.Bids[i].Qty,
		})
	}
	sort.Sort(e.BidSubBook(Book.Bids))
	Book.Bids[0].Total = Book.Bids[0].Qty
	for i := 1; i < 10; i++ {
		Book.Bids[i].Total = Book.Bids[i-1].Total + Book.Bids[i].Qty
	}

	Book.Asks = []e.SubBook{}
	for i := range c.Asks {
		Book.Asks = append(Book.Asks, e.SubBook{
			Price: c.Asks[i].Price,
			Qty:   c.Asks[i].Qty,
		})
	}
	sort.Sort(e.AskSubBook(Book.Asks))
	Book.Asks[0].Total = Book.Asks[0].Qty
	for i := 1; i < 10; i++ {
		Book.Asks[i].Total = Book.Asks[i-1].Total + Book.Asks[i].Qty
	}

	// Book.Bids = Book.Bids[:10]
	// Book.Asks = Book.Asks[:10]

	return Book
}

func isBookValid(newBook e.Book, challengeChecksum string) bool {
	var str strings.Builder

	// Literally here so I can print the current local orderbook for checksum comparisons
	dict := map[string][][]string{}

	for _, level := range newBook.Asks[:10] {
		price := decimal.NewFromFloat(level.Price).StringFixed(5)
		price = strings.Replace(price, ".", "", 1)
		price = strings.TrimLeft(price, "0")
		str.WriteString(price)

		volume := decimal.NewFromFloat(level.Qty).StringFixed(8)
		volume = strings.Replace(volume, ".", "", 1)
		volume = strings.TrimLeft(volume, "0")
		str.WriteString(volume)

		dict["a"] = append(dict["a"], []string{
			decimal.NewFromFloat(level.Price).StringFixed(5), decimal.NewFromFloat(level.Qty).StringFixed(8), "",
		})
	}
	for _, level := range newBook.Bids[:10] {
		price := decimal.NewFromFloat(level.Price).StringFixed(5)
		price = strings.Replace(price, ".", "", 1)
		price = strings.TrimLeft(price, "0")
		str.WriteString(price)

		volume := decimal.NewFromFloat(level.Qty).StringFixed(8)
		volume = strings.Replace(volume, ".", "", 1)
		volume = strings.TrimLeft(volume, "0")
		str.WriteString(volume)

		dict["b"] = append(dict["b"], []string{
			decimal.NewFromFloat(level.Price).StringFixed(5), decimal.NewFromFloat(level.Qty).StringFixed(8), "",
		})
	}

	checksum := str.String()
	checksumInt := crc32.ChecksumIEEE([]byte(checksum))
	checksumPass := fmt.Sprint(checksumInt) == challengeChecksum

	if !checksumPass {
		fmt.Println("Kraken checksum fail")
	}

	return checksumPass
}

websocket orderbook panics after initial snapshot

I'm seeing the following 'panic: interface conversion: interface {} is string, not []interface {}' immediately after the first orderbook update (the snapshot is parsed fine). Anyone seen this before?

panic send to closed channel on example auth

Hi.
Issue with send on closed channel.
Example


goroutine 72 [running]:
github.com/aopoltorzhicky/go_kraken/websocket.(*Client).close(0xc001112500, 0x0, 0x0)
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:285 +0xc8
github.com/aopoltorzhicky/go_kraken/websocket.(*Client).exit(0xc001112500, 0x0, 0x0, 0x0, 0x0)
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:258 +0x52
github.com/aopoltorzhicky/go_kraken/websocket.(*Client).reconnect(0xc001112500, 0x0, 0x0, 0x0, 0x0)
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:222 +0x7a
github.com/aopoltorzhicky/go_kraken/websocket.(*Client).listenDisconnect(0xc001112500)
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:149 +0x357
created by github.com/aopoltorzhicky/go_kraken/websocket.(*Client).reset
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:189 +0x67
panic: send on closed channel

goroutine 30 [running]:
github.com/aopoltorzhicky/go_kraken/websocket.(*Client).close(0xc001112500, 0xdc0ca0, 0xc00018e230)
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:283 +0xad
github.com/aopoltorzhicky/go_kraken/websocket.(*Client).exit(0xc001112500, 0xdc0ca0, 0xc00018e230, 0x0, 0x0)
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:258 +0x52
github.com/aopoltorzhicky/go_kraken/websocket.(*Client).reconnect(0xc001112500, 0xdc0ca0, 0xc00018e230, 0x0, 0x0)
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:245 +0x5d0
github.com/aopoltorzhicky/go_kraken/websocket.(*Client).listenDisconnect(0xc001112500)
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:149 +0x357
created by github.com/aopoltorzhicky/go_kraken/websocket.(*Client).reset
        /home/dvp/go/pkg/mod/github.com/aopoltorzhicky/go_kraken/[email protected]/client.go:189 +0x67

websocket subscribing to open orders fails

Hi,

I'm having trouble with subscribing to open orders using the websocket module.

I copied the code from examples/auth/main.go in this repo and added my own keys.

When running it from the shell I got some output telling me connecting and subscribing to my own open orders succeeded.

2020/12/12 21:49:10 [WARN]: invalid data length: []interface {}{[]interface {}{
*** snip many go-objects with info of requests from the past ***
}}, "openOrders", map[string]interface {}{"sequence":1}}

As expected with these kind of errors, I don't receive any mesage on the channel returned by Listen().

It seems the data length is 3 and not 4 as the library expects.

Can you reproduce this on your end? Should I make a PR with a fix?

Regards,
lk16

Missing ReqID field in `AddOrderRequest` and `AddOrderResponse`

Maybe I'm missing something, but the structs mentioned the title are both missing a ReqID field, which is present in Kraken API documentation. Without them I don't know how can I know which order an AddOrderResponse belongs to. Is there any reason for this omission that I should be aware of?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.